From d06fac989097df10425b76daccae7c27e769574a Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 13:24:31 -0400 Subject: [PATCH 01/15] zig fmt: add a fuzz test This fuzz test checks several properties of zig fmt are upheld, namely idempotency, textual equivilence, and no trailing whitespace. All functions in the fuzz test have @disableInstrumentation for performance and since their branches are not interesting to the fuzzer. --- lib/std/zig/parser_test.zig | 278 ++++++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index b563fa90e33c..7cefbc1688c4 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -2,6 +2,7 @@ const std = @import("std"); const mem = std.mem; const print = std.debug.print; const maxInt = std.math.maxInt; +const Token = std.zig.Token; test "zig fmt: remove extra whitespace at start and end of file with comment between" { try testTransform( @@ -6477,3 +6478,280 @@ fn fuzzTestOneParse(_: void, input: []const u8) !void { const source = allocator.dupeZ(u8, bytes) catch return; _ = std.zig.Ast.parse(allocator, source, mode) catch return; } + +test "zig fmt: fuzz" { + try std.testing.fuzz({}, fuzzRender, .{}); +} + +fn parseTokens( + fba: mem.Allocator, + source: [:0]const u8, +) error{ SkipInput, OutOfMemory }!struct { + toks: std.zig.Ast.TokenList, + maybe_rewritable: bool, + skip_idempotency: bool, +} { + @disableInstrumentation(); + // Byte-order marker is stripped + var maybe_rewritable = mem.startsWith(u8, source, "\xEF\xBB\xBF"); + var skip_idempotency = false; // This should be able to be removed once all the bugs are fixed + + var tokens: std.zig.Ast.TokenList = .{}; + try tokens.ensureTotalCapacity(fba, source.len / 2); + var tokenizer: std.zig.Tokenizer = .init(source); + while (true) { + const tok = tokenizer.next(); + switch (tok.tag) { + .invalid, + .invalid_periodasterisks, + => return error.SkipInput, + // Extra colons can be removed + // asm_legacy is converted + // TODO: asm_legacy strips comments when converting clobbers + .keyword_asm, + // Qualifiers can be reordered + // keyword_const is intentionally excluded since it is used in other contexts and + // having only one qualifier will never lead to reordering. + .keyword_addrspace, + .keyword_align, + .keyword_allowzero, + .keyword_callconv, + .keyword_linksection, + .keyword_volatile, + => maybe_rewritable = true, + .builtin, + // Pointer casts can be reordered + => for ([_][]const u8{ + "ptrCast", + "alignCast", + "addrSpaceCast", + "constCast", + "volatileCast", + }) |id| { + if (mem.eql(u8, source[tok.loc.start + 1 .. tok.loc.end], id)) { + maybe_rewritable = false; + } + }, + // Quoted identifiers can be unquoted + .identifier => maybe_rewritable = maybe_rewritable or source[tok.loc.start] == '@', + else => {}, + // #23754 + .container_doc_comment, + => if (mem.endsWith(Token.Tag, tokens.items(.tag), &.{.l_brace})) { + return error.SkipInput; + }, + // #24507 + .keyword_inline, + .keyword_for, + .keyword_while, + .l_brace, + => if (mem.endsWith(Token.Tag, tokens.items(.tag), &.{ .identifier, .colon })) { + maybe_rewritable = true; + skip_idempotency = true; + }, + } + try tokens.append(fba, .{ + .tag = tok.tag, + .start = @intCast(tok.loc.start), + }); + if (tok.tag == .eof) + break; + } + return .{ + .toks = tokens, + .maybe_rewritable = maybe_rewritable, + .skip_idempotency = skip_idempotency, + }; +} + +fn parseAstFromTokens( + fba: mem.Allocator, + source: [:0]const u8, + toks: std.zig.Ast.TokenList, +) error{OutOfMemory}!std.zig.Ast { + @disableInstrumentation(); + var parser: @import("Parse.zig") = .{ + .source = source, + .gpa = fba, + .tokens = toks.slice(), + .errors = .{}, + .nodes = .{}, + .extra_data = .{}, + .scratch = .{}, + .tok_i = 0, + }; + try parser.nodes.ensureTotalCapacity(fba, 1 + toks.len / 2); + try parser.parseRoot(); + return .{ + .source = source, + .mode = .zig, + .tokens = parser.tokens, + .nodes = parser.nodes.slice(), + .extra_data = parser.extra_data.items, + .errors = parser.errors.items, + }; +} + +/// Checks equivelence of non-whitespace characters. +/// If there are commas in `source`, then it is checked they are also present +/// in `rendered`. Extra commas in `rendered` are ignored. +fn isRewritten(source: [:0]const u8, rendered: [:0]const u8) bool { + @disableInstrumentation(); + var i: usize = 0; + for (source[0 .. source.len + 1]) |c| switch (c) { + ' ', '\r', '\t', '\n' => {}, + else => while (true) { + defer i += 1; + switch (rendered[i]) { + ' ', '\n' => {}, + ',' => if (c == ',') break, + else => |r| if (c != r) return false else break, + } + }, + }; + std.debug.assert(i >= rendered.len); + return false; +} + +/// Checks that no line ends in whitespace +fn checkBetweenTokens(src: []const u8, fmt_on: *bool) error{ + TrailingLineWhitespace, + DoubleEmptyLine, +}!void { + @disableInstrumentation(); + var pos: usize = 0; + while (true) { + const nl_pos = mem.indexOfScalarPos(u8, src, pos, '\n'); + var check_trailing = fmt_on.*; + + const line = src[pos .. nl_pos orelse src.len]; + if (mem.indexOfScalar(u8, line, '/')) |comment_start| { + const comment_content = line[comment_start..][2..]; + const trimmed_comment = mem.trim(u8, comment_content, &std.ascii.whitespace); + if (mem.eql(u8, trimmed_comment, "zig fmt: off")) { + fmt_on.* = false; + } else if (mem.eql(u8, trimmed_comment, "zig fmt: on")) { + fmt_on.* = true; + check_trailing = true; + } + } + + pos = nl_pos orelse break; + if (check_trailing and pos != 0) switch (src[pos - 1]) { + ' ', '\t', '\r' => return error.TrailingLineWhitespace, + '\n' => if (pos != 1 and src[pos - 2] == '\n') return error.DoubleEmptyLine, + else => {}, + }; + pos += 1; + } +} + +/// Ignores extre `.comma` tokens in `rendered` +fn reparseTokens( + fba: mem.Allocator, + rendered: [:0]const u8, + expected_tags: [:.eof]const Token.Tag, +) error{ + OutOfMemory, + SameLineMultilineStringLiteral, + TrailingLineWhitespace, + DoubleEmptyLine, +}!struct { + toks: std.zig.Ast.TokenList, + rewritten: bool, +} { + @disableInstrumentation(); + var rewritten = false; + var tokens: std.zig.Ast.TokenList = .{}; + var last_token_end: usize = 0; + var fmt_on = true; + + try tokens.ensureTotalCapacity(fba, expected_tags.len + 2); // 1 for EOF and 1 for maybe a comma + var tokenizer: std.zig.Tokenizer = .init(rendered); + var i: usize = 0; + while (true) { + const tok = tokenizer.next(); + try tokens.append(fba, .{ + .tag = tok.tag, + .start = @intCast(tok.loc.start), + }); + + const between = rendered[last_token_end..tok.loc.start]; + last_token_end = tok.loc.end; + try checkBetweenTokens(between, &fmt_on); + if (tok.tag == .multiline_string_literal_line and fmt_on) blk: { + if (tokens.len == 1) + break :blk; // first token + if (mem.indexOfScalar(u8, between, '\n') == null) + return error.SameLineMultilineStringLiteral; + } + if (tok.tag == expected_tags[i]) { + if (tok.tag == .eof) + break; + i += 1; + } else if (tok.tag != .comma or !fmt_on) { + rewritten = true; + } + } + std.debug.assert(i == expected_tags.len); + try checkBetweenTokens(rendered[last_token_end..], &fmt_on); + + return .{ .toks = tokens, .rewritten = rewritten }; +} + +fn fuzzRender(_: void, bytes: []const u8) !void { + @disableInstrumentation(); + var fba_ctx = std.heap.FixedBufferAllocator.init(&fixed_buffer_mem); + fuzzRenderInner(bytes, fba_ctx.allocator()) catch |e| return switch (e) { + error.SkipInput, error.OutOfMemory => {}, + else => e, + }; +} + +fn fuzzRenderInner(bytes: []const u8, fba: mem.Allocator) !void { + @disableInstrumentation(); + const source = try fba.dupeZ(u8, bytes); + var src_toks = try parseTokens(fba, source); // var for asm_legacy idempotency bug + const src_tree = try parseAstFromTokens(fba, source, src_toks.toks); + if (src_tree.errors.len != 0) + return; + for (src_tree.nodes.items(.tag)) |tag| switch (tag) { + // #24507 (`switch(x) { inline for (a) |a| a => {} }` to + // `switch(x) { { inline for (a) |a| a => {} }` since + // AST determines inline case token as one before the case expression's first) + .switch_case_inline, .switch_case_inline_one => return error.SkipInput, + // TODO: asm_legacy does not canonicalize identifiers when converting from string clobbers + .asm_legacy => src_toks.skip_idempotency = true, + else => {}, + }; + + var rendered_w: std.Io.Writer.Allocating = .init(fba); + try rendered_w.ensureUnusedCapacity(source.len + source.len / 2); + try src_tree.render(fba, &rendered_w.writer, .{}); + // `toOwnedSliceSentinel` is not used since it reallocates the entire + // list to save space which is useless for fixed buffer allocators. + try rendered_w.writer.writeByte(0); + const rendered = rendered_w.written()[0 .. rendered_w.written().len - 1 :0]; + + // First check that the non-whitespace characters match. This ensures that + // identifier names, numbers, comments, et cetera are preserved. + if (!src_toks.maybe_rewritable and isRewritten(source, rendered)) + return error.Rewritten; + // Next check that the tokens are the same since whitespace removal can change the tokens + const src_tags = src_toks.toks.items(.tag); + const rendered_toks = try reparseTokens(fba, rendered, src_tags[0 .. src_tags.len - 1 :.eof]); + if (!src_toks.maybe_rewritable and rendered_toks.rewritten) + return error.Rewritten; + + // Rerender the tree to check idempotency and that new commas + // and whitespace changes did not create an AST error. + const rendered_tree = try parseAstFromTokens(fba, rendered, rendered_toks.toks); + if (rendered_tree.errors.len != 0) + return error.Rewritten; + if (!src_toks.skip_idempotency) { + var rerendered_w: std.Io.Writer.Allocating = .init(fba); + try rerendered_w.ensureUnusedCapacity(source.len); + try rendered_tree.render(fba, &rerendered_w.writer, .{}); + try std.testing.expectEqualStrings(rendered, rerendered_w.written()); + } +} From 998ce0ffe8b60a16ddf5081799c57b4ca97a4b8f Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 13:18:09 -0400 Subject: [PATCH 02/15] zig fmt: fix many bugs with multiline string literals Adds two new space modes maybe_space and comma_maybe_space which only render the space when the next token is not a multiline string literal. This is used in a lot of places to avoid a trailing space on lines caused by multiline string literals forcing newlines. Fixes places where the node tag is compared to be a multiline string literal to instead check if the first token is a multiline string literal line. This fixes cases like `\\ ++ text`. This commit also rewrites quite a bit of code to be clearer / deduplicated. --- lib/std/zig/Ast/Render.zig | 587 +++++++++++++++++------------------- lib/std/zig/parser_test.zig | 313 +++++++++++++++++++ 2 files changed, 589 insertions(+), 311 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index bc66703774d7..1972a37f1260 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -346,20 +346,27 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const next_token_tag = tree.tokenTag(next_token); // dedent the next thing that comes after a multiline string literal - if (!ais.indentStackEmpty() and - next_token_tag != .colon and - ((next_token_tag != .semicolon and next_token_tag != .comma) or - ais.lastSpaceModeIndent() < ais.currentIndent())) + if (next_token_tag != .colon and + !ais.indentStackEmpty() and + ais.lastSpaceModeIndent() < ais.currentIndent()) { - ais.popIndent(); - try ais.pushIndent(.normal); + const indent_top = &ais.indent_stack.items[ais.indent_stack.items.len - 1]; + if (indent_top.realized) { + indent_top.realized = false; + ais.indent_count -= 1; + } } switch (space) { - .none, .space, .newline, .skip => {}, - .semicolon => if (next_token_tag == .semicolon) try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon), - .comma => if (next_token_tag == .comma) try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma), - .comma_space => if (next_token_tag == .comma) try renderToken(r, next_token, .space), + .none, .space, .newline, .maybe_space, .skip => {}, + .semicolon => if (next_token_tag == .semicolon) + try renderTokenOverrideSpaceMode(r, next_token, .newline, .semicolon), + .comma => if (next_token_tag == .comma) + try renderTokenOverrideSpaceMode(r, next_token, .newline, .comma), + .comma_space => if (next_token_tag == .comma) + try renderToken(r, next_token, .space), + .comma_maybe_space => if (next_token_tag == .comma) + try renderToken(r, next_token, .maybe_space), } }, @@ -384,11 +391,11 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const defer_token = tree.nodeMainToken(node); const maybe_payload_token, const expr = tree.nodeData(node).opt_token_and_node; - try renderToken(r, defer_token, .space); + try renderToken(r, defer_token, .maybe_space); if (maybe_payload_token.unwrap()) |payload_token| { try renderToken(r, payload_token - 1, .none); // | try renderIdentifier(r, payload_token, .none, .preserve_when_shadowing); // identifier - try renderToken(r, payload_token + 1, .space); // | + try renderToken(r, payload_token + 1, .maybe_space); // | } return renderExpression(r, expr, space); }, @@ -400,7 +407,7 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { => { const main_token = tree.nodeMainToken(node); const item = tree.nodeData(node).node; - try renderToken(r, main_token, .space); + try renderToken(r, main_token, .maybe_space); return renderExpression(r, item, space); }, @@ -409,8 +416,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const lhs, const rhs = tree.nodeData(node).node_and_node; const fallback_first = tree.firstToken(rhs); - const same_line = tree.tokensOnSameLine(main_token, fallback_first); - const after_op_space = if (same_line) Space.space else Space.newline; + const seperate_line = !tree.tokensOnSameLine(main_token, fallback_first) or + tree.tokenTag(fallback_first) == .multiline_string_literal_line; + const after_op_space: Space = if (seperate_line) .newline else .space; try renderExpression(r, lhs, .space); // target @@ -489,11 +497,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { try renderExpression(r, lhs, .space); const op_token = tree.nodeMainToken(node); try ais.pushIndent(.after_equals); - if (tree.tokensOnSameLine(op_token, op_token + 1)) { - try renderToken(r, op_token, .space); - } else { - try renderToken(r, op_token, .newline); - } + const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or + tree.tokenTag(op_token + 1) == .multiline_string_literal_line; + try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space); try renderExpression(r, rhs, space); ais.popIndent(); }, @@ -532,11 +538,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { try renderExpression(r, lhs, .space); const op_token = tree.nodeMainToken(node); try ais.pushIndent(.binop); - if (tree.tokensOnSameLine(op_token, op_token + 1)) { - try renderToken(r, op_token, .space); - } else { - try renderToken(r, op_token, .newline); - } + const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or + tree.tokenTag(op_token + 1) == .multiline_string_literal_line; + try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space); try renderExpression(r, rhs, space); ais.popIndent(); }, @@ -544,11 +548,11 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { .assign_destructure => { const full = tree.assignDestructure(node); if (full.comptime_token) |comptime_token| { - try renderToken(r, comptime_token, .space); + try renderToken(r, comptime_token, .maybe_space); } for (full.ast.variables, 0..) |variable_node, i| { - const variable_space: Space = if (i == full.ast.variables.len - 1) .space else .comma_space; + const variable_space: Space = if (i == full.ast.variables.len - 1) .maybe_space else .comma_maybe_space; switch (tree.nodeTag(variable_node)) { .global_var_decl, .local_var_decl, @@ -561,11 +565,10 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { } } try ais.pushIndent(.after_equals); - if (tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1)) { - try renderToken(r, full.ast.equal_token, .space); - } else { - try renderToken(r, full.ast.equal_token, .newline); - } + const expr_seperate_line = + !tree.tokensOnSameLine(full.ast.equal_token, full.ast.equal_token + 1) or + tree.tokenTag(full.ast.equal_token + 1) == .multiline_string_literal_line; + try renderToken(r, full.ast.equal_token, if (expr_seperate_line) .newline else .space); try renderExpression(r, full.ast.value_expr, space); ais.popIndent(); }, @@ -584,7 +587,7 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { .@"try", .@"resume", => { - try renderToken(r, tree.nodeMainToken(node), .space); + try renderToken(r, tree.nodeMainToken(node), .maybe_space); return renderExpression(r, tree.nodeData(node).node, space); }, @@ -668,30 +671,23 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { .@"break", .@"continue" => { const main_token = tree.nodeMainToken(node); const opt_label_token, const opt_target = tree.nodeData(node).opt_token_and_opt_node; - if (opt_label_token == .none and opt_target == .none) { - try renderToken(r, main_token, space); // break/continue - } else if (opt_label_token == .none and opt_target != .none) { - const target = opt_target.unwrap().?; - try renderToken(r, main_token, .space); // break/continue - try renderExpression(r, target, space); - } else if (opt_label_token != .none and opt_target == .none) { - const label_token = opt_label_token.unwrap().?; - try renderToken(r, main_token, .space); // break/continue - try renderToken(r, label_token - 1, .none); // : - try renderIdentifier(r, label_token, space, .eagerly_unquote); // identifier - } else if (opt_label_token != .none and opt_target != .none) { - const label_token = opt_label_token.unwrap().?; - const target = opt_target.unwrap().?; - try renderToken(r, main_token, .space); // break/continue + + const before_target_space: Space = if (opt_target != .none) .maybe_space else space; + const before_label_space: Space = if (opt_label_token != .none) .space else before_target_space; + + try renderToken(r, main_token, before_label_space); + if (opt_label_token.unwrap()) |label_token| { try renderToken(r, label_token - 1, .none); // : - try renderIdentifier(r, label_token, .space, .eagerly_unquote); // identifier + try renderIdentifier(r, label_token, before_target_space, .eagerly_unquote); // identifier + } + if (opt_target.unwrap()) |target| { try renderExpression(r, target, space); - } else unreachable; + } }, .@"return" => { if (tree.nodeData(node).opt_node.unwrap()) |expr| { - try renderToken(r, tree.nodeMainToken(node), .space); + try renderToken(r, tree.nodeMainToken(node), .maybe_space); try renderExpression(r, expr, space); } else { try renderToken(r, tree.nodeMainToken(node), space); @@ -954,6 +950,7 @@ fn renderArrayType( fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!void { const tree = r.tree; const main_token = ptr_type.ast.main_token; + switch (ptr_type.size) { .one => { // Since ** tokens exist and the same token is shared by two @@ -1000,11 +997,36 @@ fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!voi }, } + // .maybe_space cannot be used at the end of each qualifier since they may be reordered + const final_qual: enum { + @"volatile", + @"const", + @"addrspace", + @"align", + @"allowzero", + none, + } = if (ptr_type.volatile_token != null) + .@"volatile" + else if (ptr_type.const_token != null) + .@"const" + else if (ptr_type.ast.addrspace_node != .none) + .@"addrspace" + else if (ptr_type.ast.align_node != .none) + .@"align" + else if (ptr_type.allowzero_token != null) + .@"allowzero" + else + .none; + const final_qual_space: Space = if (tree.tokenTag(tree.firstToken(ptr_type.ast.child_type)) != + .multiline_string_literal_line) .space else .none; + if (ptr_type.allowzero_token) |allowzero_token| { - try renderToken(r, allowzero_token, .space); + const this_space: Space = if (final_qual == .@"allowzero") final_qual_space else .space; + try renderToken(r, allowzero_token, this_space); } if (ptr_type.ast.align_node.unwrap()) |align_node| { + const this_space: Space = if (final_qual == .@"align") final_qual_space else .space; const align_first = tree.firstToken(align_node); try renderToken(r, align_first - 2, .none); // align try renderToken(r, align_first - 1, .none); // lparen @@ -1015,26 +1037,29 @@ fn renderPtrType(r: *Render, ptr_type: Ast.full.PtrType, space: Space) Error!voi try renderExpression(r, bit_range_start, .none); try renderToken(r, tree.firstToken(bit_range_end) - 1, .none); // colon try renderExpression(r, bit_range_end, .none); - try renderToken(r, tree.lastToken(bit_range_end) + 1, .space); // rparen + try renderToken(r, tree.lastToken(bit_range_end) + 1, this_space); // rparen } else { - try renderToken(r, tree.lastToken(align_node) + 1, .space); // rparen + try renderToken(r, tree.lastToken(align_node) + 1, this_space); // rparen } } if (ptr_type.ast.addrspace_node.unwrap()) |addrspace_node| { + const this_space: Space = if (final_qual == .@"addrspace") final_qual_space else .space; const addrspace_first = tree.firstToken(addrspace_node); try renderToken(r, addrspace_first - 2, .none); // addrspace try renderToken(r, addrspace_first - 1, .none); // lparen try renderExpression(r, addrspace_node, .none); - try renderToken(r, tree.lastToken(addrspace_node) + 1, .space); // rparen + try renderToken(r, tree.lastToken(addrspace_node) + 1, this_space); // rparen } if (ptr_type.const_token) |const_token| { - try renderToken(r, const_token, .space); + const this_space: Space = if (final_qual == .@"const") final_qual_space else .space; + try renderToken(r, const_token, this_space); } if (ptr_type.volatile_token) |volatile_token| { - try renderToken(r, volatile_token, .space); + const this_space: Space = if (final_qual == .@"volatile") final_qual_space else unreachable; + try renderToken(r, volatile_token, this_space); } try renderExpression(r, ptr_type.ast.child_type, space); @@ -1047,12 +1072,14 @@ fn renderSlice( space: Space, ) Error!void { const tree = r.tree; - const after_start_space_bool = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or + const space_around_dots = nodeCausesSliceOpSpace(tree.nodeTag(slice.ast.start)) or if (slice.ast.end.unwrap()) |end| nodeCausesSliceOpSpace(tree.nodeTag(end)) else false; - const after_start_space = if (after_start_space_bool) Space.space else Space.none; - const after_dots_space = if (slice.ast.end != .none) - after_start_space - else if (slice.ast.sentinel != .none) Space.space else Space.none; + const after_start_space: Space = if (space_around_dots) .space else .none; + const before_sentinel_space: Space = if (slice.ast.sentinel != .none) .space else .none; + const after_dots_space: Space = if (slice.ast.end != .none) + if (space_around_dots) .maybe_space else .none + else + before_sentinel_space; try renderExpression(r, slice.ast.sliced, .none); try renderToken(r, slice.ast.lbracket, .none); // lbracket @@ -1062,8 +1089,7 @@ fn renderSlice( try renderToken(r, start_last + 1, after_dots_space); // ellipsis2 ("..") if (slice.ast.end.unwrap()) |end| { - const after_end_space = if (slice.ast.sentinel != .none) Space.space else Space.none; - try renderExpression(r, end, after_end_space); + try renderExpression(r, end, before_sentinel_space); } if (slice.ast.sentinel.unwrap()) |sentinel| { @@ -1091,7 +1117,7 @@ fn renderAsmOutput( if (tree.tokenTag(symbolic_name + 4) == .arrow) { const type_expr, const rparen = tree.nodeData(asm_output).opt_node_and_token; - try renderToken(r, symbolic_name + 4, .space); // -> + try renderToken(r, symbolic_name + 4, .maybe_space); // -> try renderExpression(r, type_expr.unwrap().?, Space.none); return renderToken(r, rparen, space); } else { @@ -1172,33 +1198,38 @@ fn renderVarDeclWithoutFixups( try renderToken(r, var_decl.ast.mut_token, .space); // var - if (var_decl.ast.type_node != .none or var_decl.ast.align_node != .none or - var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or - var_decl.ast.init_node != .none) - { - const name_space = if (var_decl.ast.type_node == .none and - (var_decl.ast.align_node != .none or - var_decl.ast.addrspace_node != .none or - var_decl.ast.section_node != .none or - var_decl.ast.init_node != .none)) - Space.space - else - Space.none; - - try renderIdentifier(r, var_decl.ast.mut_token + 1, name_space, .preserve_when_shadowing); // name - } else { - return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); // name + const last_component: enum { + value, + @"linksection", + @"addrspace", + @"align", + type, + identifier, + } = if (var_decl.ast.init_node != .none) + .value + else if (var_decl.ast.section_node != .none) + .@"linksection" + else if (var_decl.ast.addrspace_node != .none) + .@"addrspace" + else if (var_decl.ast.align_node != .none) + .@"align" + else if (var_decl.ast.type_node != .none) + .type + else + .identifier; + + if (last_component == .identifier) { + return renderIdentifier(r, var_decl.ast.mut_token + 1, space, .preserve_when_shadowing); } + const after_ident_space: Space = if (var_decl.ast.type_node != .none) .none else .space; + try renderIdentifier(r, var_decl.ast.mut_token + 1, after_ident_space, .preserve_when_shadowing); if (var_decl.ast.type_node.unwrap()) |type_node| { - try renderToken(r, var_decl.ast.mut_token + 2, Space.space); // : - if (var_decl.ast.align_node != .none or var_decl.ast.addrspace_node != .none or - var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) - { - try renderExpression(r, type_node, .space); - } else { + try renderToken(r, var_decl.ast.mut_token + 2, .maybe_space); // : + if (last_component == .type) { return renderExpression(r, type_node, space); } + try renderExpression(r, type_node, .space); } if (var_decl.ast.align_node.unwrap()) |align_node| { @@ -1208,13 +1239,10 @@ fn renderVarDeclWithoutFixups( try renderToken(r, align_kw, Space.none); // align try renderToken(r, lparen, Space.none); // ( try renderExpression(r, align_node, Space.none); - if (var_decl.ast.addrspace_node != .none or var_decl.ast.section_node != .none or - var_decl.ast.init_node != .none) - { - try renderToken(r, rparen, .space); // ) - } else { + if (last_component == .@"align") { return renderToken(r, rparen, space); // ) } + try renderToken(r, rparen, .space); // ) } if (var_decl.ast.addrspace_node.unwrap()) |addrspace_node| { @@ -1224,12 +1252,10 @@ fn renderVarDeclWithoutFixups( try renderToken(r, addrspace_kw, Space.none); // addrspace try renderToken(r, lparen, Space.none); // ( try renderExpression(r, addrspace_node, Space.none); - if (var_decl.ast.section_node != .none or var_decl.ast.init_node != .none) { - try renderToken(r, rparen, .space); // ) - } else { - try renderToken(r, rparen, .none); // ) - return renderToken(r, rparen + 1, Space.newline); // ; + if (last_component == .@"addrspace") { + return renderToken(r, rparen, space); // ) } + try renderToken(r, rparen, .space); // ) } if (var_decl.ast.section_node.unwrap()) |section_node| { @@ -1239,17 +1265,19 @@ fn renderVarDeclWithoutFixups( try renderToken(r, section_kw, Space.none); // linksection try renderToken(r, lparen, Space.none); // ( try renderExpression(r, section_node, Space.none); - if (var_decl.ast.init_node != .none) { - try renderToken(r, rparen, .space); // ) - } else { + if (last_component == .@"linksection") { return renderToken(r, rparen, space); // ) } + try renderToken(r, rparen, .space); // ) } + assert(last_component == .value); const init_node = var_decl.ast.init_node.unwrap().?; const eq_token = tree.firstToken(init_node) - 1; - const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; + const rhs_seperate_line = !tree.tokensOnSameLine(eq_token, eq_token + 1) or + tree.tokenTag(eq_token + 1) == .multiline_string_literal_line; + const eq_space: Space = if (rhs_seperate_line) .newline else .space; try ais.pushIndent(.after_equals); try renderToken(r, eq_token, eq_space); // = try renderExpression(r, init_node, space); // ; @@ -1349,8 +1377,10 @@ fn renderThenElse( const tree = r.tree; const ais = r.ais; const then_expr_is_block = nodeIsBlock(tree.nodeTag(then_expr)); + const then_expr_first_token = tree.firstToken(then_expr); const indent_then_expr = !then_expr_is_block and - !tree.tokensOnSameLine(last_prefix_token, tree.firstToken(then_expr)); + (!tree.tokensOnSameLine(last_prefix_token, then_expr_first_token) or + tree.tokenTag(then_expr_first_token) == .multiline_string_literal_line); if (indent_then_expr) try ais.pushIndent(.normal); @@ -1384,7 +1414,9 @@ fn renderThenElse( const indent_else_expr = indent_then_expr and !nodeIsBlock(tree.nodeTag(else_expr)) and - !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr)); + !nodeIsIfForWhileSwitch(tree.nodeTag(else_expr)) or + tree.tokenTag(tree.firstToken(else_expr)) == + .multiline_string_literal_line; if (indent_else_expr) { try ais.pushIndent(.normal); try renderToken(r, last_else_token, .newline); @@ -1420,44 +1452,29 @@ fn renderFor(r: *Render, for_node: Ast.full.For, space: Space) Error!void { try renderParamList(r, lparen, for_node.ast.inputs, .space); var cur = for_node.payload_token; - const pipe = std.mem.indexOfScalarPos(std.zig.Token.Tag, token_tags, cur, .pipe).?; - if (tree.tokenTag(@intCast(pipe - 1)) == .comma) { + const pipe = std.mem.indexOfScalarPos(Token.Tag, token_tags, cur, .pipe).?; + const capture_trailing_comma = token_tags[@intCast(pipe - 1)] == .comma; + + if (capture_trailing_comma) try ais.pushIndent(.normal); - try renderToken(r, cur - 1, .newline); // | - while (true) { - if (tree.tokenTag(cur) == .asterisk) { - try renderToken(r, cur, .none); // * - cur += 1; - } - try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier + try renderToken(r, cur - 1, if (capture_trailing_comma) .newline else .none); // | + while (true) { + if (token_tags[cur] == .asterisk) { + try renderToken(r, cur, .none); // * cur += 1; - if (tree.tokenTag(cur) == .comma) { - try renderToken(r, cur, .newline); // , - cur += 1; - } - if (tree.tokenTag(cur) == .pipe) { - break; - } } - ais.popIndent(); - } else { - try renderToken(r, cur - 1, .none); // | - while (true) { - if (tree.tokenTag(cur) == .asterisk) { - try renderToken(r, cur, .none); // * - cur += 1; - } - try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier + try renderIdentifier(r, cur, .none, .preserve_when_shadowing); // identifier + cur += 1; + if (token_tags[cur] == .comma) { + try renderToken(r, cur, if (capture_trailing_comma) .newline else .space); // , cur += 1; - if (tree.tokenTag(cur) == .comma) { - try renderToken(r, cur, .space); // , - cur += 1; - } - if (tree.tokenTag(cur) == .pipe) { - break; - } + } + if (token_tags[cur] == .pipe) { + break; } } + if (capture_trailing_comma) + ais.popIndent(); try renderThenElse( r, @@ -1486,97 +1503,80 @@ fn renderContainerField( }; if (field.comptime_token) |t| { - try renderToken(r, t, .space); // comptime - } - if (field.ast.type_expr == .none and field.ast.value_expr == .none) { - if (field.ast.align_expr.unwrap()) |align_expr| { - try renderIdentifier(r, field.ast.main_token, .space, quote); // name - const lparen_token = tree.firstToken(align_expr) - 1; - const align_kw = lparen_token - 1; - const rparen_token = tree.lastToken(align_expr) + 1; - try renderToken(r, align_kw, .none); // align - try renderToken(r, lparen_token, .none); // ( - try renderExpression(r, align_expr, .none); // alignment - return renderToken(r, rparen_token, .space); // ) - } - return renderIdentifierComma(r, field.ast.main_token, space, quote); // name - } - if (field.ast.type_expr != .none and field.ast.value_expr == .none) { - const type_expr = field.ast.type_expr.unwrap().?; - if (!field.ast.tuple_like) { - try renderIdentifier(r, field.ast.main_token, .none, quote); // name - try renderToken(r, field.ast.main_token + 1, .space); // : - } + try renderToken(r, t, .maybe_space); // comptime + } + + const last_component: enum { + value, + @"align", + type, + identifier, + } = if (field.ast.value_expr != .none) + .value + else if (field.ast.align_expr != .none) + .@"align" + else if (field.ast.type_expr != .none) + .type + else if (!field.ast.tuple_like) + .identifier + else + unreachable; - if (field.ast.align_expr.unwrap()) |align_expr| { - try renderExpression(r, type_expr, .space); // type - const align_token = tree.firstToken(align_expr) - 2; - try renderToken(r, align_token, .none); // align - try renderToken(r, align_token + 1, .none); // ( - try renderExpression(r, align_expr, .none); // alignment - const rparen = tree.lastToken(align_expr) + 1; - return renderTokenComma(r, rparen, space); // ) - } else { - return renderExpressionComma(r, type_expr, space); // type + if (!field.ast.tuple_like) { + if (last_component == .identifier) { + return renderIdentifierComma(r, field.ast.main_token, space, quote); // name } + const this_space: Space = if (field.ast.type_expr != .none) .none else .space; + try renderIdentifier(r, field.ast.main_token, this_space, quote); // name } - if (field.ast.type_expr == .none and field.ast.value_expr != .none) { - const value_expr = field.ast.value_expr.unwrap().?; - try renderIdentifier(r, field.ast.main_token, .space, quote); // name - if (field.ast.align_expr.unwrap()) |align_expr| { - const lparen_token = tree.firstToken(align_expr) - 1; - const align_kw = lparen_token - 1; - const rparen_token = tree.lastToken(align_expr) + 1; - try renderToken(r, align_kw, .none); // align - try renderToken(r, lparen_token, .none); // ( - try renderExpression(r, align_expr, .none); // alignment - try renderToken(r, rparen_token, .space); // ) + if (field.ast.type_expr.unwrap()) |type_expr| { + if (!field.ast.tuple_like) { + try renderToken(r, field.ast.main_token + 1, .maybe_space); // : } - try renderToken(r, field.ast.main_token + 1, .space); // = - return renderExpressionComma(r, value_expr, space); // value - } - if (!field.ast.tuple_like) { - try renderIdentifier(r, field.ast.main_token, .none, quote); // name - try renderToken(r, field.ast.main_token + 1, .space); // : - } - const type_expr = field.ast.type_expr.unwrap().?; - const value_expr = field.ast.value_expr.unwrap().?; - - try renderExpression(r, type_expr, .space); // type + if (last_component == .type) { + return renderExpressionComma(r, type_expr, space); // type + } + try renderExpression(r, type_expr, .space); // type + } if (field.ast.align_expr.unwrap()) |align_expr| { - const lparen_token = tree.firstToken(align_expr) - 1; - const align_kw = lparen_token - 1; - const rparen_token = tree.lastToken(align_expr) + 1; - try renderToken(r, align_kw, .none); // align - try renderToken(r, lparen_token, .none); // ( + const align_token = tree.firstToken(align_expr) - 2; + try renderToken(r, align_token, .none); // align + try renderToken(r, align_token + 1, .none); // ( try renderExpression(r, align_expr, .none); // alignment - try renderToken(r, rparen_token, .space); // ) + const rparen = tree.lastToken(align_expr) + 1; + if (last_component == .@"align") { + return renderTokenComma(r, rparen, space); // ) + } + try renderToken(r, rparen, .space); } - const eq_token = tree.firstToken(value_expr) - 1; - const eq_space: Space = if (tree.tokensOnSameLine(eq_token, eq_token + 1)) .space else .newline; - try ais.pushIndent(.after_equals); - try renderToken(r, eq_token, eq_space); // = + if (field.ast.value_expr.unwrap()) |value_expr| { + assert(last_component == .value); + const eq_token = tree.firstToken(value_expr) - 1; + const seperate_line = !tree.tokensOnSameLine(eq_token, eq_token + 1) or + tree.tokenTag(eq_token + 1) == .multiline_string_literal_line; + const eq_space: Space = if (seperate_line) .newline else .space; - if (eq_space == .space) { - ais.popIndent(); - try renderExpressionComma(r, value_expr, space); // value - return; - } - - const maybe_comma = tree.lastToken(value_expr) + 1; + try ais.pushIndent(.after_equals); + try renderToken(r, eq_token, eq_space); // = + if (eq_space == .space) { + ais.popIndent(); + return renderExpressionComma(r, value_expr, space); // value + } - if (tree.tokenTag(maybe_comma) == .comma) { - try renderExpression(r, value_expr, .none); // value - ais.popIndent(); - try renderToken(r, maybe_comma, .newline); - } else { - try renderExpression(r, value_expr, space); // value - ais.popIndent(); - } + const maybe_comma = tree.lastToken(value_expr) + 1; + if (tree.tokenTag(maybe_comma) == .comma) { + try renderExpression(r, value_expr, .none); // value + ais.popIndent(); + try renderToken(r, maybe_comma, .newline); + } else { + try renderExpression(r, value_expr, space); // value + ais.popIndent(); + } + } else unreachable; } fn renderBuiltinCall( @@ -1590,14 +1590,9 @@ fn renderBuiltinCall( try renderToken(r, builtin_token, .none); // @name - if (params.len == 0) { - try renderToken(r, builtin_token + 1, .none); // ( - return renderToken(r, builtin_token + 2, space); // ) - } - if (r.fixups.rebase_imported_paths) |prefix| { const slice = tree.tokenSlice(builtin_token); - if (mem.eql(u8, slice, "@import")) f: { + if (params.len != 0 and mem.eql(u8, slice, "@import")) f: { const param = params[0]; const str_lit_token = tree.nodeMainToken(param); assert(tree.tokenTag(str_lit_token) == .string_literal); @@ -1616,45 +1611,7 @@ fn renderBuiltinCall( } } - const last_param = params[params.len - 1]; - const after_last_param_token = tree.lastToken(last_param) + 1; - - if (tree.tokenTag(after_last_param_token) != .comma) { - // Render all on one line, no trailing comma. - try renderToken(r, builtin_token + 1, .none); // ( - - for (params, 0..) |param_node, i| { - const first_param_token = tree.firstToken(param_node); - if (tree.tokenTag(first_param_token) == .multiline_string_literal_line or - hasSameLineComment(tree, first_param_token - 1)) - { - try ais.pushIndent(.normal); - try renderExpression(r, param_node, .none); - ais.popIndent(); - } else { - try renderExpression(r, param_node, .none); - } - - if (i + 1 < params.len) { - const comma_token = tree.lastToken(param_node) + 1; - try renderToken(r, comma_token, .space); // , - } - } - return renderToken(r, after_last_param_token, space); // ) - } else { - // Render one param per line. - try ais.pushIndent(.normal); - try renderToken(r, builtin_token + 1, Space.newline); // ( - - for (params) |param_node| { - try ais.pushSpace(.comma); - try renderExpression(r, param_node, .comma); - ais.popSpace(); - } - ais.popIndent(); - - return renderToken(r, after_last_param_token + 1, space); // ) - } + return renderParamList(r, builtin_token + 1, params, space); } fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void { @@ -1746,7 +1703,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi }, .r_paren => break, .comma => { - try renderToken(r, last_param_token, .space); // , + try renderToken(r, last_param_token, .maybe_space); // , continue; }, else => {}, // Parameter type without a name. @@ -1756,7 +1713,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi { try renderIdentifier(r, last_param_token, .none, .preserve_when_shadowing); // name last_param_token = last_param_token + 1; - try renderToken(r, last_param_token, .space); // : + try renderToken(r, last_param_token, .maybe_space); // : last_param_token += 1; } if (tree.tokenTag(last_param_token) == .keyword_anytype) { @@ -1825,7 +1782,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi ais.popIndent(); } - try renderToken(r, rparen, .space); // ) + try renderToken(r, rparen, .maybe_space); // ) if (fn_proto.ast.align_expr.unwrap()) |align_expr| { const align_lparen = tree.firstToken(align_expr) - 1; @@ -1834,7 +1791,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi try renderToken(r, align_lparen - 1, .none); // align try renderToken(r, align_lparen, .none); // ( try renderExpression(r, align_expr, .none); - try renderToken(r, align_rparen, .space); // ) + try renderToken(r, align_rparen, .maybe_space); // ) } if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| { @@ -1844,7 +1801,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi try renderToken(r, align_lparen - 1, .none); // addrspace try renderToken(r, align_lparen, .none); // ( try renderExpression(r, addrspace_expr, .none); - try renderToken(r, align_rparen, .space); // ) + try renderToken(r, align_rparen, .maybe_space); // ) } if (fn_proto.ast.section_expr.unwrap()) |section_expr| { @@ -1854,7 +1811,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi try renderToken(r, section_lparen - 1, .none); // section try renderToken(r, section_lparen, .none); // ( try renderExpression(r, section_expr, .none); - try renderToken(r, section_rparen, .space); // ) + try renderToken(r, section_rparen, .maybe_space); // ) } if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| { @@ -1868,7 +1825,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi try renderToken(r, callconv_lparen - 1, .none); // callconv try renderToken(r, callconv_lparen, .none); // ( try renderExpression(r, callconv_expr, .none); - try renderToken(r, callconv_rparen, .space); // ) + try renderToken(r, callconv_rparen, .maybe_space); // ) } } @@ -1893,7 +1850,7 @@ fn renderSwitchCase( // render inline keyword if (switch_case.inline_token) |some| { - try renderToken(r, some, .space); + try renderToken(r, some, .maybe_space); } // Render everything before the arrow @@ -1907,33 +1864,26 @@ fn renderSwitchCase( } else { // Render on one line for (switch_case.ast.values) |value_expr| { - try renderExpression(r, value_expr, .comma_space); + try renderExpression(r, value_expr, .comma_maybe_space); } } - // Render the arrow and everything after it - const pre_target_space = if (tree.nodeTag(switch_case.ast.target_expr) == .multiline_string_literal) - // Newline gets inserted when rendering the target expr. - Space.none - else - Space.space; - const after_arrow_space: Space = if (switch_case.payload_token == null) pre_target_space else .space; - try renderToken(r, switch_case.ast.arrow_token, after_arrow_space); // => + try renderToken(r, switch_case.ast.arrow_token, .maybe_space); // => if (switch_case.payload_token) |payload_token| { try renderToken(r, payload_token - 1, .none); // pipe - const ident = payload_token + @intFromBool(tree.tokenTag(payload_token) == .asterisk); - if (tree.tokenTag(payload_token) == .asterisk) { + var ident = payload_token; + if (tree.tokenTag(ident) == .asterisk) { try renderToken(r, payload_token, .none); // asterisk + ident += 1; } try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier if (tree.tokenTag(ident + 1) == .comma) { - try renderToken(r, ident + 1, .space); // , - try renderIdentifier(r, ident + 2, .none, .preserve_when_shadowing); // identifier - try renderToken(r, ident + 3, pre_target_space); // pipe - } else { - try renderToken(r, ident + 1, pre_target_space); // pipe + ident += 2; + try renderToken(r, ident - 1, .space); // , + try renderIdentifier(r, ident, .none, .preserve_when_shadowing); // identifier } + try renderToken(r, ident + 1, .maybe_space); // pipe } try renderExpression(r, switch_case.ast.target_expr, space); @@ -2021,26 +1971,13 @@ fn renderStructInit( try ais.pushIndent(.normal); try renderToken(r, struct_init.ast.lbrace, .newline); - try renderToken(r, struct_init.ast.lbrace + 1, .none); // . - try renderIdentifier(r, struct_init.ast.lbrace + 2, .space, .eagerly_unquote); // name - // Don't output a space after the = if expression is a multiline string, - // since then it will start on the next line. - const field_node = struct_init.ast.fields[0]; - const expr = tree.nodeTag(field_node); - var space_after_equal: Space = if (expr == .multiline_string_literal) .none else .space; - try renderToken(r, struct_init.ast.lbrace + 3, space_after_equal); // = - - try ais.pushSpace(.comma); - try renderExpressionFixup(r, field_node, .comma); - ais.popSpace(); - - for (struct_init.ast.fields[1..]) |field_init| { + for (0.., struct_init.ast.fields) |i, field_init| { const init_token = tree.firstToken(field_init); - try renderExtraNewlineToken(r, init_token - 3); + if (i != 0) + try renderExtraNewlineToken(r, init_token - 3); try renderToken(r, init_token - 3, .none); // . try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name - space_after_equal = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space; - try renderToken(r, init_token - 1, space_after_equal); // = + try renderToken(r, init_token - 1, .maybe_space); // = try ais.pushSpace(.comma); try renderExpressionFixup(r, field_init, .comma); @@ -2056,8 +1993,7 @@ fn renderStructInit( const init_token = tree.firstToken(field_init); try renderToken(r, init_token - 3, .none); // . try renderIdentifier(r, init_token - 2, .space, .eagerly_unquote); // name - const space_after_equal: Space = if (tree.nodeTag(field_init) == .multiline_string_literal) .none else .space; - try renderToken(r, init_token - 1, space_after_equal); // = + try renderToken(r, init_token - 1, .maybe_space); // = try renderExpressionFixup(r, field_init, .comma_space); } } @@ -2475,6 +2411,9 @@ fn renderAsmLegacy( try ais.forcePushIndent(.normal); try renderExpression(r, asm_node.ast.template, .newline); + ais.forceLastIndent(); // Might have been dedented by a multiline string literal + assert(ais.current_line_empty); + ais.setIndentDelta(asm_indent_delta); const colon1 = tree.lastToken(asm_node.ast.template) + 1; @@ -2618,7 +2557,7 @@ fn renderAsm( const first_clobber = tree.firstToken(clobbers); try renderToken(r, first_clobber - 3, .none); try renderToken(r, first_clobber - 2, .none); - try renderToken(r, first_clobber - 1, .space); + try renderToken(r, first_clobber - 1, .maybe_space); try renderExpression(r, clobbers, .none); ais.popIndent(); return renderToken(r, asm_node.ast.rparen, space); // rparen @@ -2632,6 +2571,9 @@ fn renderAsm( try ais.forcePushIndent(.normal); try renderExpression(r, asm_node.ast.template, .newline); + ais.forceLastIndent(); // Might have been dedented by a multiline string literal + assert(ais.current_line_empty); + ais.setIndentDelta(asm_indent_delta); const colon1 = tree.lastToken(asm_node.ast.template) + 1; @@ -2709,9 +2651,10 @@ fn renderAsm( unreachable; }; - try renderToken(r, colon3, .space); // : + try renderToken(r, colon3, .maybe_space); // : const clobbers = asm_node.ast.clobbers.unwrap().?; try renderExpression(r, clobbers, .none); + ais.forceLastIndent(); // Might have been dedented by a multiline string literal ais.setIndentDelta(indent_delta); ais.popIndent(); return renderToken(r, asm_node.ast.rparen, space); // rparen @@ -2737,7 +2680,7 @@ fn renderParamList( if (params.len == 0) { try ais.pushIndent(.normal); - try renderToken(r, lparen, .none); + try renderToken(r, lparen, .none); // ( ais.popIndent(); return renderToken(r, lparen + 1, space); // ) } @@ -2772,10 +2715,7 @@ fn renderParamList( if (i + 1 < params.len) { const comma = tree.lastToken(param_node) + 1; - const next_multiline_string = - tree.tokenTag(tree.firstToken(params[i + 1])) == .multiline_string_literal_line; - const comma_space: Space = if (next_multiline_string) .none else .space; - try renderToken(r, comma, comma_space); + try renderToken(r, comma, .maybe_space); } } ais.popIndent(); @@ -2837,6 +2777,13 @@ const Space = enum { /// Additionally consume the next token if it is a semicolon. /// In either case, a newline will be inserted afterwards. semicolon, + /// If the next token is not a multiline string literal, this acts as .space, + /// otherwise this acts as .none. + maybe_space, + /// Additionally consume the next token if it is a comma. + /// In either case, a space will be inserted afterwards + /// if the following token is not a multiline string literal. + comma_maybe_space, /// Skip rendering whitespace and comments. If this is used, the caller /// *must* handle whitespace and comments manually. skip, @@ -2901,6 +2848,16 @@ fn renderSpace(r: *Render, token_index: Ast.TokenIndex, lexeme_len: usize, space try ais.insertNewline(); }, + .maybe_space => if (!comment and next_token_tag != .multiline_string_literal_line) { + try ais.writeByte(' '); + }, + + .comma_maybe_space => if (next_token_tag == .comma) { + try renderToken(r, token_index + 1, .maybe_space); + } else if (!comment) { + try ais.writeByte(' '); + }, + .skip => unreachable, } } @@ -2909,9 +2866,9 @@ fn renderOnlySpace(r: *Render, space: Space) Error!void { const ais = r.ais; switch (space) { .none => {}, - .space => try ais.writeByte(' '), + .space, .maybe_space => try ais.writeByte(' '), .newline => try ais.insertNewline(), - .comma => try ais.writeAll(",\n"), + .comma, .comma_maybe_space => try ais.writeAll(",\n"), .comma_space => try ais.writeAll(", "), .semicolon => try ais.writeAll(";\n"), .skip => unreachable, @@ -3659,11 +3616,19 @@ const AutoIndentingStream = struct { pub fn popIndent(ais: *AutoIndentingStream) void { if (ais.indent_stack.pop().?.realized) { - assert(ais.indent_count > 0); ais.indent_count -= 1; } } + /// Forces the last pushed indent to be realized + pub fn forceLastIndent(ais: *AutoIndentingStream) void { + const top = &ais.indent_stack.items[ais.indent_stack.items.len - 1]; + if (!top.realized) { + top.realized = true; + ais.indent_count += 1; + } + } + pub fn indentStackEmpty(ais: *AutoIndentingStream) bool { return ais.indent_stack.items.len == 0; } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 7cefbc1688c4..13e23e273aea 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -2606,6 +2606,19 @@ test "zig fmt: first line comment in struct initializer" { ); } +test "zig fmt: multiline string literals in struct initializer" { + try testTransform( + \\const a = .{ .a = \\ + \\+ 1}; + \\ + , + \\const a = .{ .a = + \\ \\ + \\+ 1 }; + \\ + ); +} + test "zig fmt: doc comments before struct field" { try testCanonical( \\pub const Allocator = struct { @@ -4079,6 +4092,18 @@ test "zig fmt: multiline string in array" { \\} \\ ); + + try testTransform( + \\const a = .{ k, \\ + \\}; + \\ + , + \\const a = .{ + \\ k, + \\ \\ + \\}; + \\ + ); } test "zig fmt: if type expr" { @@ -6085,6 +6110,294 @@ test "zig fmt: do not canonicalize invalid cast builtins" { ); } +test "zig fmt: whitespace with multiline strings" { + try testCanonical( + \\const a = .{ + \\ .b = + \\ \\ + \\ ++ "", + \\}; + \\const b = switch (a) { + \\ a => + \\ \\ + \\ ++ "", + \\}; + \\ + ); + + try testTransform( + \\test { + \\ a = \\ + \\ ; + \\ b = \\ + \\ (); + \\ c = x ++ \\ + \\ ; + \\ d = x catch \\ + \\ ; + \\ comptime \\ + \\ , \\ + \\ , \\ + \\ = \\ + \\ ; + \\ e = if (x) \\ + \\ else y; + \\ f = if (x) y else + \\ \\ + \\ ; + \\ comptime \\ + \\ ; + \\ errdefer \\ + \\ ; + \\ try \\ + \\ ; + \\ return \\ + \\ ; + \\ const a = asm (\\ + \\ ++ "": [a] "" (-> \\ + \\ ) :: \\ + \\ ); + \\ const a2 = asm ("" ::: \\ + \\ ); + \\ const b = x[1 + 1 .. \\ + \\ ]; + \\} + \\/// tuple type + \\comptime \\ + \\, + \\a: \\ + \\align(\\ + \\) + \\= \\ + \\, + \\const A = .{ + \\ *volatile \\ + \\ , + \\ *const \\ + \\ , + \\ *addrspace( \\ + \\ ) \\ + \\ , + \\ *align( \\ + \\ : \\ + \\ : \\ + \\ ) \\ + \\ , + \\ *allowzero \\ + \\ , + \\ *\\ + \\ , + \\ **\\ + \\ , + \\ [*]\\ + \\ , + \\ [*: \\ + \\ ]\\ + \\ , + \\ [*c]\\ + \\ , + \\ []\\ + \\ , + \\ [: \\ + \\ ]\\ + \\ , + \\ *addrspace(a) align(a) \\ + \\ , + \\}; + \\const a = blk: { + \\ break \\ + \\ ; + \\ break :blk \\ + \\ ; + \\ continue \\ + \\ ; + \\ continue :blk \\ + \\ ; + \\}; + \\const b = a(a, \\ + \\++ ""); + \\const c = @a(a, \\ + \\++ ""); + \\extern fn a(T,\\ + \\) \\ + \\; + \\extern fn b(a: \\ + \\) align(a) callconv(a) \\ + \\; + \\const d = switch (a) { \\ + \\ , 1, + \\ \\ + \\ => {}, + \\ inline \\ + \\ => {}, + \\}; + \\ + , + \\test { + \\ a = + \\ \\ + \\ ; + \\ b = + \\ \\ + \\ (); + \\ c = x ++ + \\ \\ + \\ ; + \\ d = x catch + \\ \\ + \\ ; + \\ comptime + \\ \\ + \\ , + \\ \\ + \\ , + \\ \\ + \\ = + \\ \\ + \\ ; + \\ e = if (x) + \\ \\ + \\ else + \\ y; + \\ f = if (x) y else + \\ \\ + \\ ; + \\ comptime + \\ \\ + \\ ; + \\ errdefer + \\ \\ + \\ ; + \\ try + \\ \\ + \\ ; + \\ return + \\ \\ + \\ ; + \\ const a = asm ( + \\ \\ + \\ ++ "" + \\ : [a] "" (-> + \\ \\ + \\ ), + \\ : + \\ : + \\ \\ + \\ ); + \\ const a2 = asm ("" ::: + \\ \\ + \\ ); + \\ const b = x[1 + 1 .. + \\ \\ + \\ ]; + \\} + \\/// tuple type + \\comptime + \\\\ + \\, + \\a: + \\\\ + \\align( + \\\\ + \\) = + \\ \\ + \\, + \\const A = .{ + \\ *volatile + \\ \\ + \\ , + \\ *const + \\ \\ + \\ , + \\ *addrspace( + \\ \\ + \\ ) + \\ \\ + \\ , + \\ *align( + \\ \\ + \\ : + \\ \\ + \\ : + \\ \\ + \\ ) + \\ \\ + \\ , + \\ *allowzero + \\ \\ + \\ , + \\ * + \\ \\ + \\ , + \\ ** + \\ \\ + \\ , + \\ [*] + \\ \\ + \\ , + \\ [*: + \\ \\ + \\ ] + \\ \\ + \\ , + \\ [*c] + \\ \\ + \\ , + \\ [] + \\ \\ + \\ , + \\ [: + \\ \\ + \\ ] + \\ \\ + \\ , + \\ *align(a) addrspace(a) + \\ \\ + \\ , + \\}; + \\const a = blk: { + \\ break + \\ \\ + \\ ; + \\ break :blk + \\ \\ + \\ ; + \\ continue + \\ \\ + \\ ; + \\ continue :blk + \\ \\ + \\ ; + \\}; + \\const b = a(a, + \\ \\ + \\++ ""); + \\const c = @a(a, + \\ \\ + \\++ ""); + \\extern fn a(T, + \\\\ + \\) + \\\\ + \\; + \\extern fn b(a: + \\\\ + \\) align(a) callconv(a) + \\\\ + \\; + \\const d = switch (a) { + \\ \\ + \\ , 1, + \\ \\ + \\ => {}, + \\ inline + \\ \\ + \\ => {}, + \\}; + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} From df152f69ece19858d419ee4bcef7d6c085caff30 Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 13:32:29 -0400 Subject: [PATCH 03/15] zig fmt: preserve space for field accesses of number literals --- lib/std/zig/Ast/Render.zig | 13 +++++++++---- lib/std/zig/parser_test.zig | 9 +++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 1972a37f1260..3825ab14cbf8 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -441,13 +441,18 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const dot_token = name_token - 1; try ais.pushIndent(.field_access); - try renderExpression(r, lhs, .none); - // Allow a line break between the lhs and the dot if the lhs and rhs - // are on different lines. const lhs_last_token = tree.lastToken(lhs); const same_line = tree.tokensOnSameLine(lhs_last_token, name_token); - if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) try ais.insertNewline(); + // Keeping a space after the number ensures it will not turn into a decimal number + // (e.g. "0xF .A"). + const number_space: Space = if (tree.tokenTag(lhs_last_token) == .number_literal and same_line) .space else .none; + try renderExpression(r, lhs, number_space); + + // Allow a line break between the lhs and the dot if the lhs and rhs + // are on different lines. + if (!same_line and !hasComment(tree, lhs_last_token, dot_token)) + try ais.insertNewline(); try renderToken(r, dot_token, .none); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 13e23e273aea..8174db1e5686 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6110,6 +6110,15 @@ test "zig fmt: do not canonicalize invalid cast builtins" { ); } +test "zig fmt: field accesses on number literals" { + try testCanonical( + \\const a = 0xF .A; + \\const a = 0xF + \\ .A; + \\ + ); +} + test "zig fmt: whitespace with multiline strings" { try testCanonical( \\const a = .{ From 438c55a660f8605239df8704f0319223c0fdd5d5 Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 16:37:37 -0400 Subject: [PATCH 04/15] zig fmt: implement expanding error sets when comments are present --- lib/std/zig/Ast/Render.zig | 30 +++++++++++++++++++++++++----- lib/std/zig/parser_test.zig | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 3825ab14cbf8..6752da2ae4cc 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -741,8 +741,8 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { try renderToken(r, lbrace, .none); try renderIdentifier(r, lbrace + 1, .none, .eagerly_unquote); // identifier return renderToken(r, rbrace, space); - } else if (tree.tokenTag(rbrace - 1) == .comma) { - // There is a trailing comma so render each member on a new line. + } else if (!isOneLineErrorSetDecl(tree, lbrace, rbrace)) { + // Render each member on a new line. try ais.pushIndent(.normal); try renderToken(r, lbrace, .newline); var i = lbrace + 1; @@ -755,19 +755,17 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { try renderIdentifier(r, i, .comma, .eagerly_unquote); ais.popSpace(); }, - .comma => {}, else => unreachable, } } ais.popIndent(); return renderToken(r, rbrace, space); } else { - // There is no trailing comma so render everything on one line. + // Render each member on one line. try renderToken(r, lbrace, .space); var i = lbrace + 1; while (i < rbrace) : (i += 1) { switch (tree.tokenTag(i)) { - .doc_comment => unreachable, // TODO .identifier => try renderIdentifier(r, i, .comma_space, .eagerly_unquote), .comma => {}, else => unreachable, @@ -2234,6 +2232,18 @@ fn renderArrayInit( return renderToken(r, rbrace, space); // rbrace } +fn isOneLineErrorSetDecl( + tree: Ast, + lbrace: Ast.TokenIndex, + rbrace: Ast.TokenIndex, +) bool { + // If there is a trailing comma, comment, or document comment, then render each + // item on its own line. + return tree.tokenTag(rbrace - 1) != .comma and + !hasDocComment(tree, lbrace + 1, rbrace) and + !hasComment(tree, lbrace, rbrace); +} + fn renderContainerDecl( r: *Render, container_decl_node: Ast.Node.Index, @@ -3070,6 +3080,16 @@ fn hasMultilineString(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.Tok ) != null; } +/// Returns true if there exists a doc comment between the start +/// of token `start_token` and the start of token `end_token`. +fn hasDocComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool { + return std.mem.indexOfScalar( + Token.Tag, + tree.tokens.items(.tag)[start_token..end_token], + .doc_comment, + ) != null; +} + /// Assumes that start is the first byte past the previous token and /// that end is the last byte before the next token. fn renderComments(r: *Render, start: usize, end: usize) Error!bool { diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 8174db1e5686..b1895fdc77b9 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6110,6 +6110,37 @@ test "zig fmt: do not canonicalize invalid cast builtins" { ); } +test "zig fmt: seperate errors in error sets with comments" { + try testTransform( + \\error{ + \\ /// This error is very bad! + \\ A, B} + \\ + , + \\error{ + \\ /// This error is very bad! + \\ A, + \\ B, + \\} + \\ + ); + + try testTransform( + \\error{ + \\ A, B + \\ // something important + \\} + \\ + , + \\error{ + \\ A, + \\ B, + \\ // something important + \\} + \\ + ); +} + test "zig fmt: field accesses on number literals" { try testCanonical( \\const a = 0xF .A; From 0672fc59a5daf5165fe4fad0fb0b4ccf1cc3cde6 Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 16:39:20 -0400 Subject: [PATCH 05/15] zig fmt: fix doc comments on fn paramaters without comma --- lib/std/zig/Ast/Render.zig | 29 ++++++++++++++++++++------ lib/std/zig/parser_test.zig | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 6752da2ae4cc..ccbaba14699f 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -1617,6 +1617,27 @@ fn renderBuiltinCall( return renderParamList(r, builtin_token + 1, params, space); } +fn isOneLineFnProto( + tree: Ast, + fn_proto: Ast.full.FnProto, + lparen: Ast.TokenIndex, + rparen: Ast.TokenIndex, +) bool { + const trailing_comma = tree.tokenTag(rparen - 1) == .comma; + if (trailing_comma or hasComment(tree, lparen, rparen)) + return false; + + // Check that there are no doc comments + var after_last_param = lparen + 1; + for (fn_proto.ast.params) |expr| { + // Looking before each param is insufficient since anytype is not included in `params` + if (hasDocComment(tree, after_last_param, tree.firstToken(expr))) + return false; + after_last_param = tree.lastToken(expr) + 1; + } + return !hasDocComment(tree, after_last_param, rparen); +} + fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!void { const tree = r.tree; const ais = r.ais; @@ -1677,8 +1698,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi // The params list is a sparse set that does *not* include anytype or ... parameters. - const trailing_comma = tree.tokenTag(rparen - 1) == .comma; - if (!trailing_comma and !hasComment(tree, lparen, rparen)) { + if (isOneLineFnProto(tree, fn_proto, lparen, rparen)) { // Render all on one line, no trailing comma. try renderToken(r, lparen, .none); // ( @@ -1687,10 +1707,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi while (true) { last_param_token += 1; switch (tree.tokenTag(last_param_token)) { - .doc_comment => { - try renderToken(r, last_param_token, .newline); - continue; - }, + .doc_comment => unreachable, .ellipsis3 => { try renderToken(r, last_param_token, .none); // ... break; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index b1895fdc77b9..713bd94670fc 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6438,6 +6438,47 @@ test "zig fmt: whitespace with multiline strings" { ); } +test "zig fmt: doc comments on fn parameters" { + try testTransform( + \\extern fn foo( + \\ /// Bitmap + \\ active: u64 + \\) void; + \\extern fn bar( + \\ bits: u6, + \\ /// Bitmap + \\ active: u64 + \\) void; + \\extern fn baz( + \\ /// Bitmap + \\ active: anytype + \\) void; + \\ + , + \\extern fn foo( + \\ /// Bitmap + \\ active: u64, + \\) void; + \\extern fn bar( + \\ bits: u6, + \\ /// Bitmap + \\ active: u64, + \\) void; + \\extern fn baz( + \\ /// Bitmap + \\ active: anytype, + \\) void; + \\ + ); + try testCanonical( + \\extern fn foo(x: struct { + \\ /// Bitmap + \\ active: u64, + \\}) void; + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} From 3f9418f4f9be2577ed5a010f22ff11a8906eef7f Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 16:41:48 -0400 Subject: [PATCH 06/15] zig fmt: fix array size indenting when expr becomes multiline Adds a new function becomesMultilineExpr to determine this and extracts the logic from several functions. --- lib/std/zig/Ast/Render.zig | 520 +++++++++++++++++++++++++++++++----- lib/std/zig/parser_test.zig | 242 +++++++++++++++++ 2 files changed, 699 insertions(+), 63 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index ccbaba14699f..195a29aed91d 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -645,7 +645,8 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { const lhs, const rhs = tree.nodeData(node).node_and_node; const lbracket = tree.firstToken(rhs) - 1; const rbracket = tree.lastToken(rhs) + 1; - const one_line = tree.tokensOnSameLine(lbracket, rbracket); + const one_line = tree.tokensOnSameLine(lbracket, rbracket) and + !becomesMultilineExpr(tree, rhs); const inner_space = if (one_line) Space.none else Space.newline; try renderExpression(r, lhs, .none); try ais.pushIndent(.normal); @@ -928,6 +929,389 @@ fn renderExpressionFixup(r: *Render, node: Ast.Node.Index, space: Space) Error!v } } +/// Same as becomesMultilineExpr, but returns false when `node == .none` +fn optBecomesMultilineExpr(tree: Ast, node: Ast.Node.OptionalIndex) bool { + return if (node.unwrap()) |payload| becomesMultilineExpr(tree, payload) else false; +} + +/// May return false if `node` is already multiline +fn becomesMultilineExpr(tree: Ast, node: Ast.Node.Index) bool { + // Conditions related to comments, doc comments, and multiline string literals are ignored + // since they always go to the end of the line, which already make them a multi-line + // expression (since they contain a newline). + switch (tree.nodeTag(node)) { + .identifier, + .number_literal, + .char_literal, + .unreachable_literal, + .anyframe_literal, + .string_literal, + .multiline_string_literal, + .error_value, + .enum_literal, + => return false, + .container_decl_trailing, + .container_decl_arg_trailing, + .container_decl_two_trailing, + .tagged_union_trailing, + .tagged_union_enum_tag_trailing, + .tagged_union_two_trailing, + .switch_comma, + .builtin_call_two_comma, + .builtin_call_comma, + .call_one_comma, + .call_comma, + .struct_init_one_comma, + .struct_init_dot_two_comma, + .struct_init_dot_comma, + .struct_init_comma, + .array_init_one_comma, + .array_init_dot_two_comma, + .array_init_dot_comma, + .array_init_comma, + // The following always have a non-zero amount of members + // which is also the condition for them to be multi-line. + .block, + .block_semicolon, + => return true, + .block_two, + .block_two_semicolon, + => return tree.nodeData(node).opt_node_and_opt_node[0] != .none, + .container_decl, + .container_decl_arg, + .container_decl_two, + .tagged_union, + .tagged_union_enum_tag, + .tagged_union_two, + => { + var buf: [2]Ast.Node.Index = undefined; + const full = tree.fullContainerDecl(&buf, node).?; + if (full.ast.arg.unwrap()) |arg| { + if (becomesMultilineExpr(tree, arg)) + return true; + } + // This does the same checks as `isOneLineContainerDecl`, however it avoids unnecessary + // checks related to comments and multiline strings, which would mean the container is + // already multiple lines. + for (full.ast.members) |member| { + if (tree.fullContainerField(member)) |field_full| { + for ([_]Ast.Node.OptionalIndex{ + field_full.ast.type_expr, + field_full.ast.align_expr, + field_full.ast.value_expr, + }) |opt_expr| { + if (opt_expr.unwrap()) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + } + } else return true; + } + return false; + }, + .error_set_decl => { + const lbrace, const rbrace = tree.nodeData(node).token_and_token; + return !isOneLineErrorSetDecl(tree, lbrace, rbrace); + }, + .@"switch" => { + const op, const extra_index = tree.nodeData(node).node_and_extra; + const case_range = tree.extraData(extra_index, Ast.Node.SubRange); + return @intFromEnum(case_range.end) - @intFromEnum(case_range.start) != 0 or + becomesMultilineExpr(tree, op); + }, + .for_simple, .@"for" => { + const full = tree.fullFor(node).?; + if (becomesMultilineExpr(tree, full.ast.then_expr) or + optBecomesMultilineExpr(tree, full.ast.else_expr)) + return true; + + for (full.ast.inputs) |expr| { + if (if (tree.nodeTag(expr) == .for_range) blk: { + const lhs, const rhs = tree.nodeData(expr).node_and_opt_node; + break :blk becomesMultilineExpr(tree, lhs) or optBecomesMultilineExpr(tree, rhs); + } else becomesMultilineExpr(tree, expr)) + return true; + } + const final_input_expr = full.ast.inputs[full.ast.inputs.len - 1]; + if (tree.tokenTag(tree.lastToken(final_input_expr) + 1) == .comma) + return true; + + const token_tags = tree.tokens.items(.tag); + const payload = full.payload_token; + const pipe = std.mem.indexOfScalarPos(Token.Tag, token_tags, payload, .pipe).?; + return token_tags[@intCast(pipe - 1)] == .comma; + }, + .while_simple, + .while_cont, + .@"while", + => { + const full = tree.fullWhile(node).?; + return becomesMultilineExpr(tree, full.ast.cond_expr) or + becomesMultilineExpr(tree, full.ast.then_expr) or + optBecomesMultilineExpr(tree, full.ast.cont_expr) or + optBecomesMultilineExpr(tree, full.ast.else_expr); + }, + .if_simple, + .@"if", + => { + const full = tree.fullIf(node).?; + return becomesMultilineExpr(tree, full.ast.cond_expr) or + becomesMultilineExpr(tree, full.ast.then_expr) or + optBecomesMultilineExpr(tree, full.ast.else_expr); + }, + .fn_proto_simple, + .fn_proto_multi, + .fn_proto_one, + .fn_proto, + => { + var buf: [1]Ast.Node.Index = undefined; + const fn_proto = tree.fullFnProto(&buf, node).?; + + for ([_]Ast.Node.OptionalIndex{ + fn_proto.ast.return_type, + fn_proto.ast.align_expr, + fn_proto.ast.addrspace_expr, + fn_proto.ast.section_expr, + fn_proto.ast.callconv_expr, + }) |opt_expr| { + if (opt_expr.unwrap()) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + } + for (fn_proto.ast.params) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + + const lparen = fn_proto.ast.fn_token + 1; + const return_type = fn_proto.ast.return_type.unwrap().?; + const maybe_bang = tree.firstToken(return_type) - 1; + const rparen = fnProtoRparen(tree, fn_proto, maybe_bang); + return !isOneLineFnProto(tree, fn_proto, lparen, rparen); + }, + .asm_simple, + => { + const lhs = tree.nodeData(node).node_and_token[0]; + return becomesMultilineExpr(tree, lhs); + }, + .@"asm", + => { + const lhs, const extra_index = tree.nodeData(node).node_and_extra; + const asm_extra = tree.extraData(extra_index, Ast.Node.Asm); + return @intFromEnum(asm_extra.items_end) - @intFromEnum(asm_extra.items_start) != 0 or + becomesMultilineExpr(tree, lhs) or optBecomesMultilineExpr(tree, asm_extra.clobbers); + }, + .asm_legacy, + => { + const lhs, const extra_index = tree.nodeData(node).node_and_extra; + const asm_extra = tree.extraData(extra_index, Ast.Node.AsmLegacy); + return @intFromEnum(asm_extra.items_end) - @intFromEnum(asm_extra.items_start) != 0 or + becomesMultilineExpr(tree, lhs); + }, + .array_type, .array_type_sentinel => { + const array_type = tree.fullArrayType(node).?; + const rbracket = tree.firstToken(array_type.ast.elem_type) - 1; + return !isOneLineArrayType(tree, array_type, rbracket) or + becomesMultilineExpr(tree, array_type.ast.elem_type); + }, + .array_access => { + const lhs, const rhs = tree.nodeData(node).node_and_node; + const lbracket = tree.firstToken(rhs) - 1; + const rbracket = tree.lastToken(rhs) + 1; + return !tree.tokensOnSameLine(lbracket, rbracket) or + becomesMultilineExpr(tree, lhs) or + becomesMultilineExpr(tree, rhs); + }, + .call_one, + .call, + .builtin_call_two, + .builtin_call, + .array_init_one, + .array_init_dot_two, + .array_init_dot, + .array_init, + .struct_init_one, + .struct_init_dot_two, + .struct_init_dot, + .struct_init, + => |tag| { + var buf: [2]Ast.Node.Index = undefined; + const opt_lhs: Ast.Node.OptionalIndex, const items = switch (tag) { + .call_one, .call => blk: { + const full = tree.fullCall(buf[0..1], node).?; + break :blk .{ full.ast.fn_expr.toOptional(), full.ast.params }; + }, + .builtin_call_two, .builtin_call => .{ .none, tree.builtinCallParams(&buf, node).? }, + .array_init_one, + .array_init_dot_two, + .array_init_dot, + .array_init, + => blk: { + const full = tree.fullArrayInit(&buf, node).?; + break :blk .{ full.ast.type_expr, full.ast.elements }; + }, + .struct_init_one, + .struct_init_dot_two, + .struct_init_dot, + .struct_init, + => blk: { + const full = tree.fullStructInit(&buf, node).?; + break :blk .{ full.ast.type_expr, full.ast.fields }; + }, + else => unreachable, + }; + if (opt_lhs.unwrap()) |lhs| { + if (becomesMultilineExpr(tree, lhs)) + return true; + } + for (items) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + return false; + }, + .assign_destructure => { + const full = tree.assignDestructure(node); + for (full.ast.variables) |expr| { + if (becomesMultilineExpr(tree, expr)) + return true; + } + return becomesMultilineExpr(tree, full.ast.value_expr); + }, + .ptr_type_aligned, + .ptr_type_sentinel, + .ptr_type, + .ptr_type_bit_range, + => { + const full = tree.fullPtrType(node).?; + return becomesMultilineExpr(tree, full.ast.child_type) or + optBecomesMultilineExpr(tree, full.ast.sentinel) or + optBecomesMultilineExpr(tree, full.ast.align_node) or + optBecomesMultilineExpr(tree, full.ast.addrspace_node) or + optBecomesMultilineExpr(tree, full.ast.bit_range_start) or + optBecomesMultilineExpr(tree, full.ast.bit_range_end); + }, + .slice_open, + .slice, + .slice_sentinel, + => { + const full = tree.fullSlice(node).?; + return becomesMultilineExpr(tree, full.ast.sliced) or + becomesMultilineExpr(tree, full.ast.start) or + optBecomesMultilineExpr(tree, full.ast.end) or + optBecomesMultilineExpr(tree, full.ast.sentinel); + }, + .@"comptime", + .@"nosuspend", + .@"suspend", + .@"resume", + .bit_not, + .bool_not, + .negation, + .negation_wrap, + .optional_type, + .address_of, + .deref, + .@"try", + => return becomesMultilineExpr(tree, tree.nodeData(node).node), + .@"return" => return optBecomesMultilineExpr(tree, tree.nodeData(node).opt_node), + .field_access, + .unwrap_optional, + .grouped_expression, + => return becomesMultilineExpr(tree, tree.nodeData(node).node_and_token[0]), + .add, + .add_wrap, + .add_sat, + .array_cat, + .array_mult, + .bang_equal, + .bit_and, + .bit_or, + .shl, + .shl_sat, + .shr, + .bit_xor, + .bool_and, + .bool_or, + .div, + .equal_equal, + .greater_or_equal, + .greater_than, + .less_or_equal, + .less_than, + .merge_error_sets, + .mod, + .mul, + .mul_wrap, + .mul_sat, + .sub, + .sub_wrap, + .sub_sat, + .@"orelse", + .@"catch", + .error_union, + .assign, + .assign_bit_and, + .assign_bit_or, + .assign_shl, + .assign_shl_sat, + .assign_shr, + .assign_bit_xor, + .assign_div, + .assign_sub, + .assign_sub_wrap, + .assign_sub_sat, + .assign_mod, + .assign_add, + .assign_add_wrap, + .assign_add_sat, + .assign_mul, + .assign_mul_wrap, + .assign_mul_sat, + => { + const lhs, const rhs = tree.nodeData(node).node_and_node; + return becomesMultilineExpr(tree, lhs) or becomesMultilineExpr(tree, rhs); + }, + .@"break", .@"continue" => { + const opt_expr = tree.nodeData(node).opt_token_and_opt_node[1]; + return optBecomesMultilineExpr(tree, opt_expr); + }, + .anyframe_type => return becomesMultilineExpr(tree, tree.nodeData(node).token_and_node[1]), + .@"errdefer", + .@"defer", + .for_range, + .switch_range, + .switch_case_one, + .switch_case_inline_one, + .switch_case, + .switch_case_inline, + .asm_output, + .asm_input, + .fn_decl, + .container_field, + .container_field_init, + .container_field_align, + .root, + .global_var_decl, + .local_var_decl, + .simple_var_decl, + .aligned_var_decl, + .test_decl, + => unreachable, + } +} + +fn isOneLineArrayType( + tree: Ast, + array_type: Ast.full.ArrayType, + rbracket: Ast.TokenIndex, +) bool { + return tree.tokensOnSameLine(array_type.ast.lbracket, rbracket) and + !becomesMultilineExpr(tree, array_type.ast.elem_count) and + !optBecomesMultilineExpr(tree, array_type.ast.sentinel); +} + fn renderArrayType( r: *Render, array_type: Ast.full.ArrayType, @@ -936,7 +1320,7 @@ fn renderArrayType( const tree = r.tree; const ais = r.ais; const rbracket = tree.firstToken(array_type.ast.elem_type) - 1; - const one_line = tree.tokensOnSameLine(array_type.ast.lbracket, rbracket); + const one_line = isOneLineArrayType(tree, array_type, rbracket); const inner_space = if (one_line) Space.none else Space.newline; try ais.pushIndent(.normal); try renderToken(r, array_type.ast.lbracket, inner_space); // lbracket @@ -1617,6 +2001,47 @@ fn renderBuiltinCall( return renderParamList(r, builtin_token + 1, params, space); } +fn fnProtoRparen(tree: Ast, fn_proto: Ast.full.FnProto, maybe_bang: Ast.TokenIndex) Ast.TokenIndex { + // These may appear in any order, so we have to check the token_starts array + // to find out which is first. + var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang; + var smallest_start = tree.tokenStart(maybe_bang); + if (fn_proto.ast.align_expr.unwrap()) |align_expr| { + const tok = tree.firstToken(align_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| { + const tok = tree.firstToken(addrspace_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + if (fn_proto.ast.section_expr.unwrap()) |section_expr| { + const tok = tree.firstToken(section_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| { + const tok = tree.firstToken(callconv_expr) - 3; + const start = tree.tokenStart(tok); + if (start < smallest_start) { + rparen = tok; + smallest_start = start; + } + } + assert(tree.tokenTag(rparen) == .r_paren); + return rparen; +} + fn isOneLineFnProto( tree: Ast, fn_proto: Ast.full.FnProto, @@ -1655,46 +2080,7 @@ fn renderFnProto(r: *Render, fn_proto: Ast.full.FnProto, space: Space) Error!voi const return_type = fn_proto.ast.return_type.unwrap().?; const maybe_bang = tree.firstToken(return_type) - 1; - const rparen = blk: { - // These may appear in any order, so we have to check the token_starts array - // to find out which is first. - var rparen = if (tree.tokenTag(maybe_bang) == .bang) maybe_bang - 1 else maybe_bang; - var smallest_start = tree.tokenStart(maybe_bang); - if (fn_proto.ast.align_expr.unwrap()) |align_expr| { - const tok = tree.firstToken(align_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - if (fn_proto.ast.addrspace_expr.unwrap()) |addrspace_expr| { - const tok = tree.firstToken(addrspace_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - if (fn_proto.ast.section_expr.unwrap()) |section_expr| { - const tok = tree.firstToken(section_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - if (fn_proto.ast.callconv_expr.unwrap()) |callconv_expr| { - const tok = tree.firstToken(callconv_expr) - 3; - const start = tree.tokenStart(tok); - if (start < smallest_start) { - rparen = tok; - smallest_start = start; - } - } - break :blk rparen; - }; - assert(tree.tokenTag(rparen) == .r_paren); + const rparen = fnProtoRparen(tree, fn_proto, maybe_bang); // The params list is a sparse set that does *not* include anytype or ... parameters. @@ -2261,6 +2647,34 @@ fn isOneLineErrorSetDecl( !hasComment(tree, lbrace, rbrace); } +fn isOneLineContainerDecl( + tree: Ast, + container_decl: Ast.full.ContainerDecl, + lbrace: Ast.TokenIndex, + rbrace: Ast.TokenIndex, +) bool { + // We print all the members in one-line unless one of the following conditions are true: + + // 1. The container has comments or multiline strings. + if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) { + return false; + } + + // 2. The container has a container comment. + if (tree.tokenTag(lbrace + 1) == .container_doc_comment) return false; + + // 3. A member of the container has a doc comment. + if (hasDocComment(tree, lbrace + 1, rbrace)) + return false; + + // 4. The container has non-field members. + for (container_decl.ast.members) |member| { + if (tree.fullContainerField(member) == null) return false; + } + + return true; +} + fn renderContainerDecl( r: *Render, container_decl_node: Ast.Node.Index, @@ -2325,27 +2739,7 @@ fn renderContainerDecl( } const src_has_trailing_comma = tree.tokenTag(rbrace - 1) == .comma; - if (!src_has_trailing_comma) one_line: { - // We print all the members in-line unless one of the following conditions are true: - - // 1. The container has comments or multiline strings. - if (hasComment(tree, lbrace, rbrace) or hasMultilineString(tree, lbrace, rbrace)) { - break :one_line; - } - - // 2. The container has a container comment. - if (tree.tokenTag(lbrace + 1) == .container_doc_comment) break :one_line; - - // 3. A member of the container has a doc comment. - for (tree.tokens.items(.tag)[lbrace + 1 .. rbrace - 1]) |tag| { - if (tag == .doc_comment) break :one_line; - } - - // 4. The container has non-field members. - for (container_decl.ast.members) |member| { - if (tree.fullContainerField(member) == null) break :one_line; - } - + if (!src_has_trailing_comma and isOneLineContainerDecl(tree, container_decl, lbrace, rbrace)) { // Print all the declarations on the same line. try renderToken(r, lbrace, .space); // lbrace for (container_decl.ast.members) |member| { diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 713bd94670fc..82a194391f80 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6150,6 +6150,248 @@ test "zig fmt: field accesses on number literals" { ); } +test "zig fmt: array indent when inner becomes multi-line" { + try testTransform( + \\const access_block = x[{{}}]; + \\ + \\const block = [{{}}]T; + \\const sentinel_block = [15:{{}}]T; + \\const container = [enum { A, }]T; + \\const container_arg = [union({{}}) {}]T; + \\const error_set = [error{ A, }]T; + \\const @"switch" = [switch (m) { 0 => {}, }]T; + \\const switch_op = [switch ({{}}) {}]T; + \\const for_capture = [for (a) |_,| {}]T; + \\const for_expr = [for ({{}}) |_| {}]T; + \\const for_range = [for ({{}}..15) |_| {}]T; + \\const for_input_comma = [for (0..15,) |_| {}]T; + \\const call_param = [a({{}})]T; + \\const call_fn = [({{}})()]T; + \\const builtin_call = [@log2({{}})]T; + \\const array_init = [.{{{}}}]T; + \\const struct_init = [.{.x = {{}}}]T; + \\const @"asm" = [asm ("" : [x] "" (-> T))]T; + \\const asm_template = [asm ({{}})]T; + \\const asm_clobbers = [asm ("" ::: {{}})]T; + \\const @"fn" = [fn (({{}})) void]T; + \\const array_type_len = [[{{}}]T]T; + \\const array_type_type = [[1]({{}})]T; + \\const array_access_array = [({{}})[1]]T; + \\const array_access_index = [x[{{}}]]T; + \\const binop = [1 + {{}}]T; + \\const unaryop = [!{{}}]T; + \\const destructure = [while (true) : (x, {{}} = z) {}]T; + \\ + , + \\const access_block = x[ + \\ { + \\ {} + \\ } + \\]; + \\ + \\const block = [ + \\ { + \\ {} + \\ } + \\]T; + \\const sentinel_block = [ + \\ 15 + \\ : + \\ { + \\ {} + \\ } + \\]T; + \\const container = [ + \\ enum { + \\ A, + \\ } + \\]T; + \\const container_arg = [ + \\ union({ + \\ {} + \\ }) {} + \\]T; + \\const error_set = [ + \\ error{ + \\ A, + \\ } + \\]T; + \\const @"switch" = [ + \\ switch (m) { + \\ 0 => {}, + \\ } + \\]T; + \\const switch_op = [ + \\ switch ({ + \\ {} + \\ }) {} + \\]T; + \\const for_capture = [ + \\ for (a) | + \\ _, + \\ | {} + \\]T; + \\const for_expr = [ + \\ for ({ + \\ {} + \\ }) |_| {} + \\]T; + \\const for_range = [ + \\ for ({ + \\ {} + \\ }..15) |_| {} + \\]T; + \\const for_input_comma = [ + \\ for ( + \\ 0..15, + \\ ) |_| {} + \\]T; + \\const call_param = [ + \\ a({ + \\ {} + \\ }) + \\]T; + \\const call_fn = [ + \\ ({ + \\ {} + \\ })() + \\]T; + \\const builtin_call = [ + \\ @log2({ + \\ {} + \\ }) + \\]T; + \\const array_init = [ + \\ .{{ + \\ {} + \\ }} + \\]T; + \\const struct_init = [ + \\ .{ .x = { + \\ {} + \\ } } + \\]T; + \\const @"asm" = [ + \\ asm ("" + \\ : [x] "" (-> T), + \\ ) + \\]T; + \\const asm_template = [ + \\ asm ({ + \\ {} + \\ }) + \\]T; + \\const asm_clobbers = [ + \\ asm ("" ::: { + \\ {} + \\ }) + \\]T; + \\const @"fn" = [ + \\ fn (({ + \\ {} + \\ })) void + \\]T; + \\const array_type_len = [ + \\ [ + \\ { + \\ {} + \\ } + \\ ]T + \\]T; + \\const array_type_type = [ + \\ [1]({ + \\ {} + \\ }) + \\]T; + \\const array_access_array = [ + \\ ({ + \\ {} + \\ })[1] + \\]T; + \\const array_access_index = [ + \\ x[ + \\ { + \\ {} + \\ } + \\ ] + \\]T; + \\const binop = [ + \\ 1 + { + \\ {} + \\ } + \\]T; + \\const unaryop = [ + \\ !{ + \\ {} + \\ } + \\]T; + \\const destructure = [ + \\ while (true) : (x, { + \\ {} + \\ } = z) {} + \\]T; + \\ + ); + + try testCanonical( + \\const oneline_access_block = x[{}]; + \\const oneline_block = [{}]T; + \\const oneline_sentinel_block = [15:{}]T; + \\const oneline_container = [enum { A }]T; + \\const oneline_error_set = [error{A}]T; + \\const oneline_switch = [switch (m) {}]T; + \\const oneline_for = [for (a, 0..15) |_, _| {}]T; + \\const oneline_call = [a(a)]T; + \\const oneline_builtin_call = [@log2(a)]T; + \\const oneline_array_init = [.{a}]T; + \\const oneline_struct_init = [.{ .x = a }]T; + \\const oneline_asm = [asm ("" ::: .{})]T; + \\const onlinee_fn = [fn (usize) void]T; + \\const oneline_array_type = [[{}]T]T; + \\const oneline_array_access = [x[{}]]T; + \\const online_binop = [1 + 1]T; + \\const online_unaryop = [!false]T; + \\const oneline_destructure = [while (true) : (x, y = z) {}]T; + \\ + ); + + try testTransform( + \\const a = [{ + \\}]T; + \\ + \\const b = x[{ + \\}]; + \\ + \\const c = [ + \\ { + \\ } + \\]T; + \\ + \\const d = x[ + \\ { + \\ } + \\]; + \\ + , + \\const a = [ + \\ {} + \\]T; + \\ + \\const b = x[ + \\ {} + \\]; + \\ + \\const c = [ + \\ {} + \\]T; + \\ + \\const d = x[ + \\ {} + \\]; + \\ + ); +} + test "zig fmt: whitespace with multiline strings" { try testCanonical( \\const a = .{ From 4a56827c91aa435402e7d221cc491408b9e79ac2 Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:01:55 -0400 Subject: [PATCH 07/15] Ast: fix pointer types with subexprs containing qualifiers --- lib/std/zig/Ast.zig | 18 ++++++++++-------- lib/std/zig/parser_test.zig | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 72ca20a6c0a6..dc4b2d84efa5 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -2140,10 +2140,12 @@ fn fullPtrTypeComponents(tree: Ast, info: full.PtrType.Components) full.PtrType // here while looking for modifiers as that could result in false // positives. Therefore, start after a sentinel if there is one and // skip over any align node and bit range nodes. - var i = if (info.sentinel.unwrap()) |sentinel| tree.lastToken(sentinel) + 1 else switch (size) { - .many, .c => info.main_token + 1, - else => info.main_token, - }; + var i = (if (info.sentinel.unwrap()) |sentinel| tree.lastToken(sentinel) + 1 else switch (size) { + .one => info.main_token, + .slice => info.main_token + 1, + .many => info.main_token + 2, + .c => info.main_token + 3, + }) + 1; const end = tree.firstToken(info.child_type); while (i < end) : (i += 1) { switch (tree.tokenTag(i)) { @@ -2151,15 +2153,15 @@ fn fullPtrTypeComponents(tree: Ast, info: full.PtrType.Components) full.PtrType .keyword_const => result.const_token = i, .keyword_volatile => result.volatile_token = i, .keyword_align => { - const align_node = info.align_node.unwrap().?; if (info.bit_range_end.unwrap()) |bit_range_end| { assert(info.bit_range_start != .none); i = tree.lastToken(bit_range_end) + 1; - } else { + } else if (info.align_node.unwrap()) |align_node| { i = tree.lastToken(align_node) + 1; - } + } else unreachable; }, - else => {}, + .keyword_addrspace => i = tree.lastToken(info.addrspace_node.unwrap().?) + 1, + else => unreachable, } } return result; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 82a194391f80..12928a9a1b84 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -7029,6 +7029,23 @@ test "ampersand" { , &.{}); } +test "Ast: pointer types with subexprs containing qualifiers" { + var fixed_allocator = std.heap.FixedBufferAllocator.init(fixed_buffer_mem[0..]); + const allocator = fixed_allocator.allocator(); + var tree = try std.zig.Ast.parse(allocator, "**addrspace(*align(1)T)T", .zon); + defer tree.deinit(allocator); + + const regular_ptr_node = tree.nodeData(.root).node; + const full_regular_ptr = tree.fullPtrType(regular_ptr_node) orelse return error.TestFailed; + try std.testing.expect(full_regular_ptr.ast.addrspace_node == .none); + try std.testing.expect(full_regular_ptr.ast.align_node == .none); + + const special_ptr_node = full_regular_ptr.ast.child_type; + const full_special_ptr = tree.fullPtrType(special_ptr_node) orelse return error.TestFailed; + try std.testing.expect(full_special_ptr.ast.addrspace_node != .none); + try std.testing.expect(full_special_ptr.ast.align_node == .none); +} + var fixed_buffer_mem: [100 * 1024]u8 = undefined; fn testParse(source: [:0]const u8, allocator: mem.Allocator, anything_changed: *bool) ![]u8 { From b564f633bff055cb27c2477e53fce235e226ddec Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:03:20 -0400 Subject: [PATCH 08/15] Ast: fix comptime wrapper for destructure --- lib/std/zig/Parse.zig | 14 +++++++++----- lib/std/zig/parser_test.zig | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index 4d8c71081644..612cd55606c0 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -919,11 +919,15 @@ fn expectStatement(p: *Parse, allow_defer_var: bool) Error!Node.Index { } else { const assign = try p.expectAssignExpr(); try p.expectSemicolon(.expected_semi_after_stmt, true); - return p.addNode(.{ - .tag = .@"comptime", - .main_token = comptime_token, - .data = .{ .node = assign }, - }); + if (p.nodeTag(assign) != .assign_destructure) { + return p.addNode(.{ + .tag = .@"comptime", + .main_token = comptime_token, + .data = .{ .node = assign }, + }); + } else { + return assign; + } } } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 12928a9a1b84..de6f30b478db 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -2949,6 +2949,7 @@ test "zig fmt: destructure" { \\ comptime w, var y = .{ 3, 4 }; \\ comptime var z, x = .{ 5, 6 }; \\ comptime y, z = .{ 7, 8 }; + \\ if (false) unreachable else comptime a, b = .{ 9, 10 }; \\} \\ ); From 9420a4a2a35606f0bcb3c02be40a39ec9cd2e488 Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:10:18 -0400 Subject: [PATCH 09/15] zig fmt: canonicalize stray backslashes in identifiers --- lib/std/zig/Ast/Render.zig | 3 +++ lib/std/zig/parser_test.zig | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 195a29aed91d..3f013053e2e2 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -3449,6 +3449,9 @@ fn renderIdentifierContents(ais: *AutoIndentingStream, bytes: []const u8) !void } }, .failure => { + // Escape the stray backslash + // This also avoids cases like "\x3\x39" becoming "\x39" + try ais.writeByte('\\'); try ais.writeAll(escape_sequence); }, } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index de6f30b478db..d4ddb11360a9 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6722,6 +6722,24 @@ test "zig fmt: doc comments on fn parameters" { ); } +test "zig fmt: canonicalize stray backslashes in identifiers" { + try testTransform( + \\const @"\x" = undefined; + \\const @"\x3" = undefined; + \\const @"\x3\x39" = undefined; + \\const @"\u{" = undefined; + \\const @"\?" = undefined; + \\ + , + \\const @"\\x" = undefined; + \\const @"\\x3" = undefined; + \\const @"\\x39" = undefined; + \\const @"\\u{" = undefined; + \\const @"\\?" = undefined; + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} From d2fe73193d13e8c0b9cb349bda142f0a6b170dde Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:07:46 -0400 Subject: [PATCH 10/15] zig fmt: properly check escape sequences --- lib/std/zig/Ast/Render.zig | 2 +- lib/std/zig/parser_test.zig | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 3f013053e2e2..f03ca0a10e55 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -3359,7 +3359,7 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote }, .failure => return renderQuotedIdentifier(r, token_index, space, false), } - contents_i += esc_offset; + contents_i = esc_offset; continue; }, else => return renderQuotedIdentifier(r, token_index, space, false), diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index d4ddb11360a9..8677f2eaee0a 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6142,6 +6142,16 @@ test "zig fmt: seperate errors in error sets with comments" { ); } +test "zig fmt: proper escape checks" { + try testTransform( + \\@"\x41\x42\!" + \\ + , + \\@"AB\\!" + \\ + ); +} + test "zig fmt: field accesses on number literals" { try testCanonical( \\const a = 0xF .A; From 955a0db0495a4ea9dcd3ecab477d8b8d36c68c1a Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:18:14 -0400 Subject: [PATCH 11/15] zig fmt: fix tracking of applied indentation --- lib/std/zig/Ast/Render.zig | 4 ++-- lib/std/zig/parser_test.zig | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index f03ca0a10e55..facf828e6073 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -4075,8 +4075,8 @@ const AutoIndentingStream = struct { /// Writes ' ' bytes if the current line is empty fn applyIndent(ais: *AutoIndentingStream) Error!void { const current_indent = ais.currentIndent(); - if (ais.current_line_empty and current_indent > 0) { - if (ais.disabled_offset == null) { + if (ais.current_line_empty) { + if (current_indent > 0 and ais.disabled_offset == null) { try ais.underlying_writer.splatByteAll(' ', current_indent); } ais.applied_indent = current_indent; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 8677f2eaee0a..1c3c1f6ceb4c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6732,6 +6732,23 @@ test "zig fmt: doc comments on fn parameters" { ); } +test "zig fmt: proper tracking of indentation" { + try testCanonical( + \\const a = { + \\ {} + \\}; + \\const b = if (x) {}; + \\const c = .{ + \\ { + \\ {} + \\ } // + \\ , + \\ if (x) {}, + \\}; + \\ + ); +} + test "zig fmt: canonicalize stray backslashes in identifiers" { try testTransform( \\const @"\x" = undefined; From 0792f4c14afde4ef947f2ad16a858143018681be Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:23:11 -0400 Subject: [PATCH 12/15] zig fmt: fix asm_legacy conversion with quoted identifier renderSpace takes the length of the original lexeme, so passing the length of the new lexeme is not correct. --- lib/std/zig/Ast/Render.zig | 5 ++--- lib/std/zig/parser_test.zig | 13 +++++++++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index facf828e6073..6ea9bfdbefa4 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -3673,7 +3673,7 @@ fn tokenSliceForRender(tree: Ast, token_index: Ast.TokenIndex) []const u8 { return ret; } -fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usize { +fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) Error!usize { const tree = r.tree; const ais = r.ais; assert(tree.tokenTag(token_index) == .string_literal); @@ -3681,12 +3681,11 @@ fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) !usiz const unquoted = lexeme[1..][0 .. lexeme.len - 2]; if (std.zig.isValidId(unquoted)) { try ais.writeAll(unquoted); - return unquoted.len; } else { try ais.writeByte('@'); try ais.writeAll(lexeme); - return lexeme.len + 1; } + return lexeme.len; } fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool { diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 1c3c1f6ceb4c..f91897f6b635 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6767,6 +6767,19 @@ test "zig fmt: canonicalize stray backslashes in identifiers" { ); } +test "zig fmt: asm_legacy conversion with quoted identifier" { + try testTransform( + \\const a = asm (x :: [L] "" (q) : ""); + , + \\const a = asm (x + \\ : + \\ : [L] "" (q), + \\ : .{ .@"" = true } + \\); + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} From 5d32c3a9127200f45a2ccb391d43887e07a2b54b Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Mon, 28 Jul 2025 14:25:35 -0400 Subject: [PATCH 13/15] zig fmt: rewrite renderArrayInit There were just too many bugs. This new implementation supports zig fmt: on/off. It also puts expressions with unicode characters on their own line, which avoids issues with aligning them. --- lib/std/zig/Ast/Render.zig | 295 ++++++++++++++---------------------- lib/std/zig/parser_test.zig | 127 +++++++++++++++- 2 files changed, 234 insertions(+), 188 deletions(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index 6ea9bfdbefa4..b81c834f70f8 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -2467,170 +2467,132 @@ fn renderArrayInit( try ais.pushIndent(.normal); try renderToken(r, array_init.ast.lbrace, .newline); - - var expr_index: usize = 0; - while (true) { - const row_size = rowSize(tree, array_init.ast.elements[expr_index..], rbrace); - const row_exprs = array_init.ast.elements[expr_index..]; - // A place to store the width of each expression and its column's maximum - const widths = try gpa.alloc(usize, row_exprs.len + row_size); - defer gpa.free(widths); - @memset(widths, 0); - - const expr_newlines = try gpa.alloc(bool, row_exprs.len); - defer gpa.free(expr_newlines); - @memset(expr_newlines, false); - - const expr_widths = widths[0..row_exprs.len]; - const column_widths = widths[row_exprs.len..]; - - // Find next row with trailing comment (if any) to end the current section. - const section_end = sec_end: { - var this_line_first_expr: usize = 0; - var this_line_size = rowSize(tree, row_exprs, rbrace); - for (row_exprs, 0..) |expr, i| { - // Ignore comment on first line of this section. - if (i == 0) continue; - const expr_last_token = tree.lastToken(expr); - if (tree.tokensOnSameLine(tree.firstToken(row_exprs[0]), expr_last_token)) - continue; - // Track start of line containing comment. - if (!tree.tokensOnSameLine(tree.firstToken(row_exprs[this_line_first_expr]), expr_last_token)) { - this_line_first_expr = i; - this_line_size = rowSize(tree, row_exprs[this_line_first_expr..], rbrace); - } - - const maybe_comma = expr_last_token + 1; - if (tree.tokenTag(maybe_comma) == .comma) { - if (hasSameLineComment(tree, maybe_comma)) - break :sec_end i - this_line_size + 1; - } - } - break :sec_end row_exprs.len; - }; - expr_index += section_end; - - const section_exprs = row_exprs[0..section_end]; - - var sub_expr_buffer: Writer.Allocating = .init(gpa); - defer sub_expr_buffer.deinit(); - - const sub_expr_buffer_starts = try gpa.alloc(usize, section_exprs.len + 1); - defer gpa.free(sub_expr_buffer_starts); - - var auto_indenting_stream: AutoIndentingStream = .init(gpa, &sub_expr_buffer.writer, indent_delta); - defer auto_indenting_stream.deinit(); - var sub_render: Render = .{ + try ais.pushSpace(.comma); + + const expr_widths = try gpa.alloc(enum(usize) { + /// The expression contains non-printable characters (e.g. unicode / newlines) + /// or has formatting disabled at the start or end. + nonprint = std.math.maxInt(usize), + _, + }, array_init.ast.elements.len); + defer gpa.free(expr_widths); + { + var buf: Writer.Allocating = .init(gpa); + defer buf.deinit(); + var sub_ais: AutoIndentingStream = .init(gpa, &buf.writer, indent_delta); + sub_ais.disabled_offset = ais.disabled_offset; + defer sub_ais.deinit(); + var sub_r: Render = .{ .gpa = r.gpa, - .ais = &auto_indenting_stream, + .ais = &sub_ais, .tree = r.tree, .fixups = r.fixups, }; - - // Calculate size of columns in current section - var column_counter: usize = 0; - var single_line = true; - var contains_newline = false; - for (section_exprs, 0..) |expr, i| { - const start = sub_expr_buffer.written().len; - sub_expr_buffer_starts[i] = start; - - if (i + 1 < section_exprs.len) { - try renderExpression(&sub_render, expr, .none); - const written = sub_expr_buffer.written(); - const width = written.len - start; - const this_contains_newline = mem.indexOfScalar(u8, written[start..], '\n') != null; - contains_newline = contains_newline or this_contains_newline; - expr_widths[i] = width; - expr_newlines[i] = this_contains_newline; - - if (!this_contains_newline) { - const column = column_counter % row_size; - column_widths[column] = @max(column_widths[column], width); - - const expr_last_token = tree.lastToken(expr) + 1; - const next_expr = section_exprs[i + 1]; - column_counter += 1; - if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(next_expr))) single_line = false; - } else { - single_line = false; - column_counter = 0; - } + for (array_init.ast.elements, expr_widths) |e, *width| { + const begin_disabled = sub_ais.disabled_offset != null; + // `.skip` space so trailing commments aren't included + try renderExpressionComma(&sub_r, e, .skip); + if (!begin_disabled and sub_ais.disabled_offset == null) { + const w = buf.written(); + width.* = for (w) |c| { + if (!std.ascii.isPrint(c)) + break .nonprint; + } else @enumFromInt(w.len - @intFromBool(w[w.len - 1] == ',')); } else { - try ais.pushSpace(.comma); - try renderExpression(&sub_render, expr, .comma); - ais.popSpace(); - - const written = sub_expr_buffer.written(); - const width = written.len - start - 2; - const this_contains_newline = mem.indexOfScalar(u8, written[start .. written.len - 1], '\n') != null; - contains_newline = contains_newline or this_contains_newline; - expr_widths[i] = width; - expr_newlines[i] = contains_newline; - - if (!contains_newline) { - const column = column_counter % row_size; - column_widths[column] = @max(column_widths[column], width); - } + width.* = .nonprint; } - } - sub_expr_buffer_starts[section_exprs.len] = sub_expr_buffer.written().len; - // Render exprs in current section. - column_counter = 0; - for (section_exprs, 0..) |expr, i| { - const start = sub_expr_buffer_starts[i]; - const end = sub_expr_buffer_starts[i + 1]; - const expr_text = sub_expr_buffer.written()[start..end]; - if (!expr_newlines[i]) { - try ais.writeAll(expr_text); - } else { - var by_line = std.mem.splitScalar(u8, expr_text, '\n'); - var last_line_was_empty = false; - try ais.writeAll(by_line.first()); - while (by_line.next()) |line| { - if (std.mem.startsWith(u8, line, "//") and last_line_was_empty) { - try ais.insertNewline(); - } else { - try ais.maybeInsertNewline(); - } - last_line_was_empty = (line.len == 0); - try ais.writeAll(line); - } - } - - if (i + 1 < section_exprs.len) { - const next_expr = section_exprs[i + 1]; - const comma = tree.lastToken(expr) + 1; + // Write trailing comments since they may enable/disable zig fmt + buf.clearRetainingCapacity(); + var after_expr = tree.lastToken(e); + after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma); + try renderSpace(&sub_r, after_expr, tokenSliceForRender(tree, after_expr).len, .none); - if (column_counter != row_size - 1) { - if (!expr_newlines[i] and !expr_newlines[i + 1]) { - // Neither the current or next expression is multiline - try renderToken(r, comma, .space); // , - assert(column_widths[column_counter % row_size] >= expr_widths[i]); - const padding = column_widths[column_counter % row_size] - expr_widths[i]; - try ais.splatByteAll(' ', padding); + buf.clearRetainingCapacity(); + } + } - column_counter += 1; - continue; - } - } + var remaining_exprs = array_init.ast.elements; + var remaining_widths = expr_widths; + while (remaining_exprs.len != 0) { + var row_size: usize = 1; + for (1.., remaining_exprs, remaining_widths) |len, e, w| { + if (w == .nonprint) break; + row_size = len; - if (single_line and row_size != 1) { - try renderToken(r, comma, .space); // , - continue; + var after_expr = tree.lastToken(e); + after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma); + assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace); + if (!tree.tokensOnSameLine(after_expr, after_expr + 1)) + break; + } else { + // All the expressions are on the same line. + // However, if there is a trailing comma, we put them each on their own line. + if (tree.tokenTag(rbrace - 1) == .comma) + row_size = 1; + } + + // Determine the size of this section + const section_end = end: { + var line_start = row_size; // Start after the first row to ignore comments on it + break :end for (line_start.., remaining_exprs[line_start..]) |i, e| { + const expr_first = tree.firstToken(e); + // Any nonprint character terminates the line because they are always put on their + // own line, so they will not end up on the same line as the trailing comment. + if (expr_widths[i - 1] == .nonprint or !tree.tokensOnSameLine(expr_first - 1, expr_first)) { + line_start = i; } - column_counter = 0; - try renderToken(r, comma, .newline); // , - try renderExtraNewline(r, next_expr); + var after_expr = tree.lastToken(e); + after_expr += @intFromBool(tree.tokenTag(after_expr + 1) == .comma); + assert(tree.tokenTag(after_expr) == .comma or after_expr + 1 == rbrace); + if (hasTrailingComment(tree, after_expr)) + break line_start; + } else remaining_exprs.len; + }; + const section_exprs = remaining_exprs[0..section_end]; + const section_widths = remaining_widths[0..section_end]; + remaining_exprs = remaining_exprs[section_end..]; + remaining_widths = remaining_widths[section_end..]; + + // Determine the width of each column + var col_widths = try gpa.alloc(usize, row_size); + defer gpa.free(col_widths); + @memset(col_widths, 0); + + var col: usize = 0; + for (section_widths) |w| { + if (w == .nonprint) { + col = 0; + continue; + } + col_widths[col] = @max(col_widths[col], @intFromEnum(w)); + col += 1; + if (col == row_size) { + col = 0; } } - if (expr_index == array_init.ast.elements.len) - break; + // Render each expression + col = 0; + for (0.., section_exprs, section_widths) |i, e, w| { + if (i + 1 == section_end or col + 1 == row_size or + w == .nonprint or section_widths[i + 1] == .nonprint) + { + try renderExpression(r, e, .comma); + col = 0; + if (i + 1 != section_end) { + try renderExtraNewline(r, section_exprs[i + 1]); + } + } else { + try renderExpression(r, e, .comma_space); + try ais.splatByteAll(' ', col_widths[col] - @intFromEnum(w)); + col += 1; + } + } } + ais.popSpace(); ais.popIndent(); return renderToken(r, rbrace, space); // rbrace } @@ -3478,7 +3440,7 @@ fn hasComment(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) const token: Ast.TokenIndex = @intCast(i); const start = tree.tokenStart(token) + tree.tokenSlice(token).len; const end = tree.tokenStart(token + 1); - if (mem.indexOf(u8, tree.source[start..end], "//") != null) return true; + if (mem.indexOfScalar(u8, tree.source[start..end], '/') != null) return true; } return false; @@ -3688,9 +3650,10 @@ fn writeStringLiteralAsIdentifier(r: *Render, token_index: Ast.TokenIndex) Error return lexeme.len; } -fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool { - const between_source = tree.source[tree.tokenStart(token_index)..tree.tokenStart(token_index + 1)]; - for (between_source) |byte| switch (byte) { +fn hasTrailingComment(tree: Ast, t: Ast.TokenIndex) bool { + const start = tree.tokenStart(t) + tree.tokenSlice(t).len; + const between = tree.source[start..tree.tokenStart(t + 1)]; + for (between) |byte| switch (byte) { '\n' => return false, '/' => return true, else => continue, @@ -3702,12 +3665,7 @@ fn hasSameLineComment(tree: Ast, token_index: Ast.TokenIndex) bool { /// start_token and end_token. fn anythingBetween(tree: Ast, start_token: Ast.TokenIndex, end_token: Ast.TokenIndex) bool { if (start_token + 1 != end_token) return true; - const between_source = tree.source[tree.tokenStart(start_token)..tree.tokenStart(start_token + 1)]; - for (between_source) |byte| switch (byte) { - '/' => return true, - else => continue, - }; - return false; + return hasComment(tree, start_token, end_token); } fn writeFixingWhitespace(w: *Writer, slice: []const u8) Error!void { @@ -3794,29 +3752,6 @@ fn nodeCausesSliceOpSpace(tag: Ast.Node.Tag) bool { }; } -// Returns the number of nodes in `exprs` that are on the same line as `rtoken`. -fn rowSize(tree: Ast, exprs: []const Ast.Node.Index, rtoken: Ast.TokenIndex) usize { - const first_token = tree.firstToken(exprs[0]); - if (tree.tokensOnSameLine(first_token, rtoken)) { - const maybe_comma = rtoken - 1; - if (tree.tokenTag(maybe_comma) == .comma) - return 1; - return exprs.len; // no newlines - } - - var count: usize = 1; - for (exprs, 0..) |expr, i| { - if (i + 1 < exprs.len) { - const expr_last_token = tree.lastToken(expr) + 1; - if (!tree.tokensOnSameLine(expr_last_token, tree.firstToken(exprs[i + 1]))) return count; - count += 1; - } else { - return count; - } - } - unreachable; -} - /// Automatically inserts indentation of written data by keeping /// track of the current indentation level /// diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index f91897f6b635..2259340cf30a 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1393,12 +1393,30 @@ test "zig fmt: comment to disable/enable zig fmt" { \\const c = d; \\// zig fmt: on \\const e = f; + \\const g = .{ + \\ h, i, + \\ // zig fmt: off + \\ j, + \\ k, + \\ // zig fmt: on + \\ l, m, n, o, + \\}; + \\ , \\const a = b; \\// zig fmt: off \\const c = d; \\// zig fmt: on \\const e = f; + \\const g = .{ + \\ h, i, + \\ // zig fmt: off + \\ j, + \\ k, + \\ // zig fmt: on + \\ l, m, + \\ n, o, + \\}; \\ ); } @@ -2050,6 +2068,38 @@ test "zig fmt: array literal vertical column alignment" { \\ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; \\const a = [12]u8{ \\ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, }; + \\const a = .{ + \\ 1, \\ + \\ , 2, + \\ 3, + \\}; + \\const a = .{ + \\ \\ + \\ , 1, 2, + \\ 3, + \\}; + \\const a = .{ + \\ {{}}, 1, + \\ 2, 3, + \\}; + \\const a = .{ + \\ a, bb // + \\ , ccc, dddd, + \\}; + \\const a = .{ + \\ "a", "b", "ä", "a", "123", + \\}; + \\const a = .{ + \\ a, a, .{ + \\ // zig fmt: off + \\ }, + \\ a*a, a, + \\ .{ + \\ // zig fmt: on + \\ }, aa, + \\ // zig fmt: off + \\ a*a, + \\}; \\ , \\const a = []u8{ @@ -2078,6 +2128,53 @@ test "zig fmt: array literal vertical column alignment" { \\ 30, \\ 31, \\}; + \\const a = .{ + \\ 1, + \\ \\ + \\ , + \\ 2, + \\ 3, + \\}; + \\const a = .{ + \\ \\ + \\ , + \\ 1, + \\ 2, + \\ 3, + \\}; + \\const a = .{ + \\ { + \\ {} + \\ }, + \\ 1, + \\ 2, + \\ 3, + \\}; + \\const a = .{ + \\ a, + \\ bb // + \\ , + \\ ccc, + \\ dddd, + \\}; + \\const a = .{ + \\ "a", "b", + \\ "ä", + \\ "a", "123", + \\}; + \\const a = .{ + \\ a, a, + \\ .{ + \\ // zig fmt: off + \\ }, + \\ a*a, a, + \\ .{ + \\ // zig fmt: on + \\ }, + \\ aa, + \\ // zig fmt: off + \\ a*a, + \\}; \\ ); } @@ -4887,8 +4984,8 @@ test "zig fmt: multiline string literals should play nice with array initializer \\ 0, \\ }}}}}}}}; \\ myFunc(.{ - \\ "aaaaaaa", "bbbbbb", "ccccc", - \\ "dddd", ("eee"), ("fff"), + \\ "aaaaaaa", "bbbbbb", "ccccc", + \\ "dddd", ("eee"), ("fff"), \\ ("gggg"), \\ // Line comment \\ \\Multiline String Literals can be quite long @@ -4917,11 +5014,9 @@ test "zig fmt: multiline string literals should play nice with array initializer \\ ( \\ \\ xxx \\ ), - \\ "xxx", - \\ "xxx", + \\ "xxx", "xxx", \\ }, - \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, - \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, + \\ .{ "xxxxxxx", "xxx", "xxx", "xxx" }, .{ "xxxxxxx", "xxx", "xxx", "xxx" }, \\ "aaaaaaa", "bbbbbb", "ccccc", // - \\ "dddd", ("eee"), ("fff"), \\ .{ @@ -4929,8 +5024,7 @@ test "zig fmt: multiline string literals should play nice with array initializer \\ ( \\ \\ xxx \\ ), - \\ "xxxxxxxxxxxxxx", - \\ "xxx", + \\ "xxxxxxxxxxxxxx", "xxx", \\ }, \\ .{ \\ ( @@ -6732,6 +6826,23 @@ test "zig fmt: doc comments on fn parameters" { ); } +test "zig fmt: array literal formatting when element becomes multiline" { + try testTransform( + \\const a = .{a,{{}}, + \\ b,c,}; + , + \\const a = .{ + \\ a, + \\ { + \\ {} + \\ }, + \\ b, + \\ c, + \\}; + \\ + ); +} + test "zig fmt: proper tracking of indentation" { try testCanonical( \\const a = { From 25c6f9bacb0395c86f316e944e346ec396ae08f7 Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Tue, 29 Jul 2025 09:25:41 -0400 Subject: [PATCH 14/15] zig fmt: fix error sets with empty line before comma --- lib/std/zig/Ast/Render.zig | 7 ++++++- lib/std/zig/parser_test.zig | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index b81c834f70f8..c32878bfeea8 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -748,8 +748,13 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { try renderToken(r, lbrace, .newline); var i = lbrace + 1; while (i < rbrace) : (i += 1) { + const tag = tree.tokenTag(i); + if (tag == .comma) { + assert(tree.tokenTag(i - 1) == .identifier); + continue; + } if (i > lbrace + 1) try renderExtraNewlineToken(r, i); - switch (tree.tokenTag(i)) { + switch (tag) { .doc_comment => try renderToken(r, i, .newline), .identifier => { try ais.pushSpace(.comma); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 2259340cf30a..f0ab7f275767 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6891,6 +6891,22 @@ test "zig fmt: asm_legacy conversion with quoted identifier" { ); } +test "zig fmt: error set with extra newline before comma" { + try testTransform( + \\const E = error{ + \\ A + \\ + \\ , + \\}; + \\ + , + \\const E = error{ + \\ A, + \\}; + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} From 7d24a431bd0cdc839077b57f6ce4eef9be05dd3e Mon Sep 17 00:00:00 2001 From: Kendall Condon Date: Tue, 25 Nov 2025 18:18:21 -0500 Subject: [PATCH 15/15] zig build fmt --- lib/compiler/aro/aro/Compilation.zig | 3 ++- lib/compiler/translate-c/ast.zig | 6 +++-- lib/std/crypto/aes.zig | 3 +-- lib/std/unicode.zig | 36 +++++++++++++++------------- lib/std/zig/BuiltinFn.zig | 15 ++++++++---- lib/std/zig/Parse.zig | 24 ++++++++++++------- src/codegen/sparc64/abi.zig | 4 ++-- src/link.zig | 3 ++- 8 files changed, 57 insertions(+), 37 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index c98badcc2296..0a02ff6fa63f 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -1700,7 +1700,8 @@ pub fn initSearchPath(comp: *Compilation, includes: []const Include, verbose: bo std.debug.print("#include <...> search starts here:\n", .{}); } std.debug.print(" {s}{s}\n", .{ - include.path, if (include.kind.isFramework()) + include.path, + if (include.kind.isFramework()) " (framework directory)" else "", diff --git a/lib/compiler/translate-c/ast.zig b/lib/compiler/translate-c/ast.zig index b7a8fd488afa..0b03ec411e93 100644 --- a/lib/compiler/translate-c/ast.zig +++ b/lib/compiler/translate-c/ast.zig @@ -1213,7 +1213,8 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .tag = .slice, .main_token = l_bracket, .data = .{ .node_and_extra = .{ - string, try c.addExtra(std.zig.Ast.Node.Slice{ + string, + try c.addExtra(std.zig.Ast.Node.Slice{ .start = start, .end = end, }), @@ -2720,7 +2721,8 @@ fn renderCall(c: *Context, lhs: NodeIndex, args: []const Node) !NodeIndex { .tag = .call, .main_token = lparen, .data = .{ .node_and_extra = .{ - lhs, try c.addExtra(NodeSubRange{ + lhs, + try c.addExtra(NodeSubRange{ .start = span.start, .end = span.end, }), diff --git a/lib/std/crypto/aes.zig b/lib/std/crypto/aes.zig index 130b461a5ebe..2bf659184ede 100644 --- a/lib/std/crypto/aes.zig +++ b/lib/std/crypto/aes.zig @@ -8,8 +8,7 @@ const has_armaes = builtin.cpu.has(.aarch64, .aes); // C backend doesn't currently support passing vectors to inline asm. const impl = if (builtin.cpu.arch == .x86_64 and builtin.zig_backend != .stage2_c and has_aesni and has_avx) impl: { break :impl @import("aes/aesni.zig"); -} else if (builtin.cpu.arch == .aarch64 and builtin.zig_backend != .stage2_c and has_armaes) -impl: { +} else if (builtin.cpu.arch == .aarch64 and builtin.zig_backend != .stage2_c and has_armaes) impl: { break :impl @import("aes/armcrypto.zig"); } else impl: { break :impl @import("aes/soft.zig"); diff --git a/lib/std/unicode.zig b/lib/std/unicode.zig index 1aae6d488fbe..3aa8603638bb 100644 --- a/lib/std/unicode.zig +++ b/lib/std/unicode.zig @@ -1245,14 +1245,16 @@ test utf8ToUtf16Le { const length = try utf8ToUtf16Le(utf16le[0..], "This string has been designed to test the vectorized implementat" ++ "ion by beginning with one hundred twenty-seven ASCII characters¡"); try testing.expectEqualSlices(u8, &.{ - 'T', 0, 'h', 0, 'i', 0, 's', 0, ' ', 0, 's', 0, 't', 0, 'r', 0, 'i', 0, 'n', 0, 'g', 0, ' ', 0, 'h', 0, 'a', 0, 's', 0, ' ', 0, - 'b', 0, 'e', 0, 'e', 0, 'n', 0, ' ', 0, 'd', 0, 'e', 0, 's', 0, 'i', 0, 'g', 0, 'n', 0, 'e', 0, 'd', 0, ' ', 0, 't', 0, 'o', 0, - ' ', 0, 't', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, 't', 0, 'h', 0, 'e', 0, ' ', 0, 'v', 0, 'e', 0, 'c', 0, 't', 0, 'o', 0, 'r', 0, - 'i', 0, 'z', 0, 'e', 0, 'd', 0, ' ', 0, 'i', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0, 'm', 0, 'e', 0, 'n', 0, 't', 0, 'a', 0, 't', 0, - 'i', 0, 'o', 0, 'n', 0, ' ', 0, 'b', 0, 'y', 0, ' ', 0, 'b', 0, 'e', 0, 'g', 0, 'i', 0, 'n', 0, 'n', 0, 'i', 0, 'n', 0, 'g', 0, - ' ', 0, 'w', 0, 'i', 0, 't', 0, 'h', 0, ' ', 0, 'o', 0, 'n', 0, 'e', 0, ' ', 0, 'h', 0, 'u', 0, 'n', 0, 'd', 0, 'r', 0, 'e', 0, - 'd', 0, ' ', 0, 't', 0, 'w', 0, 'e', 0, 'n', 0, 't', 0, 'y', 0, '-', 0, 's', 0, 'e', 0, 'v', 0, 'e', 0, 'n', 0, ' ', 0, 'A', 0, - 'S', 0, 'C', 0, 'I', 0, 'I', 0, ' ', 0, 'c', 0, 'h', 0, 'a', 0, 'r', 0, 'a', 0, 'c', 0, 't', 0, 'e', 0, 'r', 0, 's', 0, '¡', 0, + 'T', 0, 'h', 0, 'i', 0, 's', 0, ' ', 0, 's', 0, 't', 0, 'r', 0, 'i', 0, 'n', 0, 'g', 0, ' ', 0, 'h', 0, 'a', 0, 's', 0, ' ', 0, + 'b', 0, 'e', 0, 'e', 0, 'n', 0, ' ', 0, 'd', 0, 'e', 0, 's', 0, 'i', 0, 'g', 0, 'n', 0, 'e', 0, 'd', 0, ' ', 0, 't', 0, 'o', 0, + ' ', 0, 't', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, 't', 0, 'h', 0, 'e', 0, ' ', 0, 'v', 0, 'e', 0, 'c', 0, 't', 0, 'o', 0, 'r', 0, + 'i', 0, 'z', 0, 'e', 0, 'd', 0, ' ', 0, 'i', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0, 'm', 0, 'e', 0, 'n', 0, 't', 0, 'a', 0, 't', 0, + 'i', 0, 'o', 0, 'n', 0, ' ', 0, 'b', 0, 'y', 0, ' ', 0, 'b', 0, 'e', 0, 'g', 0, 'i', 0, 'n', 0, 'n', 0, 'i', 0, 'n', 0, 'g', 0, + ' ', 0, 'w', 0, 'i', 0, 't', 0, 'h', 0, ' ', 0, 'o', 0, 'n', 0, 'e', 0, ' ', 0, 'h', 0, 'u', 0, 'n', 0, 'd', 0, 'r', 0, 'e', 0, + 'd', 0, ' ', 0, 't', 0, 'w', 0, 'e', 0, 'n', 0, 't', 0, 'y', 0, '-', 0, 's', 0, 'e', 0, 'v', 0, 'e', 0, 'n', 0, ' ', 0, 'A', 0, + 'S', 0, 'C', 0, 'I', 0, 'I', 0, ' ', 0, 'c', 0, 'h', 0, 'a', 0, 'r', 0, 'a', 0, 'c', 0, 't', 0, 'e', 0, 'r', 0, 's', 0, + '¡', + 0, }, mem.sliceAsBytes(utf16le[0..length])); } } @@ -1317,14 +1319,16 @@ test utf8ToUtf16LeAllocZ { "ion by beginning with one hundred twenty-seven ASCII characters¡"); defer testing.allocator.free(utf16); try testing.expectEqualSlices(u8, &.{ - 'T', 0, 'h', 0, 'i', 0, 's', 0, ' ', 0, 's', 0, 't', 0, 'r', 0, 'i', 0, 'n', 0, 'g', 0, ' ', 0, 'h', 0, 'a', 0, 's', 0, ' ', 0, - 'b', 0, 'e', 0, 'e', 0, 'n', 0, ' ', 0, 'd', 0, 'e', 0, 's', 0, 'i', 0, 'g', 0, 'n', 0, 'e', 0, 'd', 0, ' ', 0, 't', 0, 'o', 0, - ' ', 0, 't', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, 't', 0, 'h', 0, 'e', 0, ' ', 0, 'v', 0, 'e', 0, 'c', 0, 't', 0, 'o', 0, 'r', 0, - 'i', 0, 'z', 0, 'e', 0, 'd', 0, ' ', 0, 'i', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0, 'm', 0, 'e', 0, 'n', 0, 't', 0, 'a', 0, 't', 0, - 'i', 0, 'o', 0, 'n', 0, ' ', 0, 'b', 0, 'y', 0, ' ', 0, 'b', 0, 'e', 0, 'g', 0, 'i', 0, 'n', 0, 'n', 0, 'i', 0, 'n', 0, 'g', 0, - ' ', 0, 'w', 0, 'i', 0, 't', 0, 'h', 0, ' ', 0, 'o', 0, 'n', 0, 'e', 0, ' ', 0, 'h', 0, 'u', 0, 'n', 0, 'd', 0, 'r', 0, 'e', 0, - 'd', 0, ' ', 0, 't', 0, 'w', 0, 'e', 0, 'n', 0, 't', 0, 'y', 0, '-', 0, 's', 0, 'e', 0, 'v', 0, 'e', 0, 'n', 0, ' ', 0, 'A', 0, - 'S', 0, 'C', 0, 'I', 0, 'I', 0, ' ', 0, 'c', 0, 'h', 0, 'a', 0, 'r', 0, 'a', 0, 'c', 0, 't', 0, 'e', 0, 'r', 0, 's', 0, '¡', 0, + 'T', 0, 'h', 0, 'i', 0, 's', 0, ' ', 0, 's', 0, 't', 0, 'r', 0, 'i', 0, 'n', 0, 'g', 0, ' ', 0, 'h', 0, 'a', 0, 's', 0, ' ', 0, + 'b', 0, 'e', 0, 'e', 0, 'n', 0, ' ', 0, 'd', 0, 'e', 0, 's', 0, 'i', 0, 'g', 0, 'n', 0, 'e', 0, 'd', 0, ' ', 0, 't', 0, 'o', 0, + ' ', 0, 't', 0, 'e', 0, 's', 0, 't', 0, ' ', 0, 't', 0, 'h', 0, 'e', 0, ' ', 0, 'v', 0, 'e', 0, 'c', 0, 't', 0, 'o', 0, 'r', 0, + 'i', 0, 'z', 0, 'e', 0, 'd', 0, ' ', 0, 'i', 0, 'm', 0, 'p', 0, 'l', 0, 'e', 0, 'm', 0, 'e', 0, 'n', 0, 't', 0, 'a', 0, 't', 0, + 'i', 0, 'o', 0, 'n', 0, ' ', 0, 'b', 0, 'y', 0, ' ', 0, 'b', 0, 'e', 0, 'g', 0, 'i', 0, 'n', 0, 'n', 0, 'i', 0, 'n', 0, 'g', 0, + ' ', 0, 'w', 0, 'i', 0, 't', 0, 'h', 0, ' ', 0, 'o', 0, 'n', 0, 'e', 0, ' ', 0, 'h', 0, 'u', 0, 'n', 0, 'd', 0, 'r', 0, 'e', 0, + 'd', 0, ' ', 0, 't', 0, 'w', 0, 'e', 0, 'n', 0, 't', 0, 'y', 0, '-', 0, 's', 0, 'e', 0, 'v', 0, 'e', 0, 'n', 0, ' ', 0, 'A', 0, + 'S', 0, 'C', 0, 'I', 0, 'I', 0, ' ', 0, 'c', 0, 'h', 0, 'a', 0, 'r', 0, 'a', 0, 'c', 0, 't', 0, 'e', 0, 'r', 0, 's', 0, + '¡', + 0, }, mem.sliceAsBytes(utf16)); } } diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index cdc63f9666da..2bb6f6578a7a 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -384,28 +384,32 @@ pub const list = list: { }, }, .{ - "@cVaArg", .{ + "@cVaArg", + .{ .tag = .c_va_arg, .param_count = 2, .illegal_outside_function = true, }, }, .{ - "@cVaCopy", .{ + "@cVaCopy", + .{ .tag = .c_va_copy, .param_count = 1, .illegal_outside_function = true, }, }, .{ - "@cVaEnd", .{ + "@cVaEnd", + .{ .tag = .c_va_end, .param_count = 1, .illegal_outside_function = true, }, }, .{ - "@cVaStart", .{ + "@cVaStart", + .{ .tag = .c_va_start, .param_count = 0, .illegal_outside_function = true, @@ -1042,7 +1046,8 @@ pub const list = list: { }, }, .{ - "@workItemId", .{ + "@workItemId", + .{ .tag = .work_item_id, .param_count = 1, .illegal_outside_function = true, diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index 612cd55606c0..7dd93fca1ad3 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -885,7 +885,8 @@ fn expectContainerField(p: *Parse) !Node.Index { .tag = .container_field, .main_token = main_token, .data = .{ .node_and_extra = .{ - type_expr, try p.addExtra(Node.ContainerField{ + type_expr, + try p.addExtra(Node.ContainerField{ .align_expr = align_expr.?, .value_expr = value_expr.?, }), @@ -1188,7 +1189,8 @@ fn expectIfStatement(p: *Parse) !Node.Index { .tag = .@"if", .main_token = if_token, .data = .{ .node_and_extra = .{ - condition, try p.addExtra(Node.If{ + condition, + try p.addExtra(Node.If{ .then_expr = then_expr, .else_expr = else_expr, }), @@ -1369,7 +1371,8 @@ fn parseWhileStatement(p: *Parse) !?Node.Index { .tag = .@"while", .main_token = while_token, .data = .{ .node_and_extra = .{ - condition, try p.addExtra(Node.While{ + condition, + try p.addExtra(Node.While{ .cont_expr = .fromOptional(cont_expr), .then_expr = then_expr, .else_expr = else_expr, @@ -1948,7 +1951,8 @@ fn parseTypeExpr(p: *Parse) Error!?Node.Index { .tag = .array_type_sentinel, .main_token = lbracket, .data = .{ .node_and_extra = .{ - len_expr.?, try p.addExtra(Node.ArrayTypeSentinel{ + len_expr.?, + try p.addExtra(Node.ArrayTypeSentinel{ .sentinel = sentinel.?, .elem_type = elem_type, }), @@ -2743,7 +2747,8 @@ fn parseWhileTypeExpr(p: *Parse) !?Node.Index { .tag = .while_cont, .main_token = while_token, .data = .{ .node_and_extra = .{ - condition, try p.addExtra(Node.WhileCont{ + condition, + try p.addExtra(Node.WhileCont{ .cont_expr = cont_expr.?, .then_expr = then_expr, }), @@ -2757,7 +2762,8 @@ fn parseWhileTypeExpr(p: *Parse) !?Node.Index { .tag = .@"while", .main_token = while_token, .data = .{ .node_and_extra = .{ - condition, try p.addExtra(Node.While{ + condition, + try p.addExtra(Node.While{ .cont_expr = .fromOptional(cont_expr), .then_expr = then_expr, .else_expr = else_expr, @@ -3235,7 +3241,8 @@ fn parseSuffixOp(p: *Parse, lhs: Node.Index) !?Node.Index { .tag = .slice_sentinel, .main_token = lbracket, .data = .{ .node_and_extra = .{ - lhs, try p.addExtra(Node.SliceSentinel{ + lhs, + try p.addExtra(Node.SliceSentinel{ .start = index_expr, .end = .fromOptional(opt_end_expr), .sentinel = sentinel, @@ -3258,7 +3265,8 @@ fn parseSuffixOp(p: *Parse, lhs: Node.Index) !?Node.Index { .tag = .slice, .main_token = lbracket, .data = .{ .node_and_extra = .{ - lhs, try p.addExtra(Node.Slice{ + lhs, + try p.addExtra(Node.Slice{ .start = index_expr, .end = end_expr, }), diff --git a/src/codegen/sparc64/abi.zig b/src/codegen/sparc64/abi.zig index a26ab9a20b7d..5b5cf552d0a2 100644 --- a/src/codegen/sparc64/abi.zig +++ b/src/codegen/sparc64/abi.zig @@ -38,10 +38,10 @@ const allocatable_regs = [_]Register{ }; pub const c_abi_int_param_regs_caller_view = [_]Register{ .o0, .o1, .o2, .o3, .o4, .o5 }; -pub const c_abi_int_param_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3", .@"i4", .@"i5" }; +pub const c_abi_int_param_regs_callee_view = [_]Register{ .i0, .i1, .i2, .i3, .i4, .i5 }; pub const c_abi_int_return_regs_caller_view = [_]Register{ .o0, .o1, .o2, .o3 }; -pub const c_abi_int_return_regs_callee_view = [_]Register{ .@"i0", .@"i1", .@"i2", .@"i3" }; +pub const c_abi_int_return_regs_callee_view = [_]Register{ .i0, .i1, .i2, .i3 }; pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_regs); diff --git a/src/link.zig b/src/link.zig index 6be88490b07c..0c58951f253f 100644 --- a/src/link.zig +++ b/src/link.zig @@ -2040,7 +2040,8 @@ fn resolveLibInput( const test_path: Path = .{ .root_dir = lib_directory, .sub_path = try std.fmt.allocPrint(arena, "{s}{s}{s}", .{ - target.libPrefix(), lib_name, switch (link_mode) { + target.libPrefix(), lib_name, + switch (link_mode) { .static => target.staticLibSuffix(), .dynamic => target.dynamicLibSuffix(), },