authorgravatar for squeek502@hotmail.comRyan Liptak <squeek502@hotmail.com> 2026-07-23 23:44:43-07:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-22 12:21:43+02:00
logcb715212f7aa62ec33933138c3cd47efdf95c075
treeb49c3fddb003abb8026f027057ca8d912a297788
parentc57dfa2f06e088b67ace125f84e2d97de5396c41

Alternative zig fmt --upgrade implementation using Render.Fixups


4 files changed, 116 insertions(+), 45 deletions(-)

lib/std/zig/Ast.zig+4-2
...@@ -139,7 +139,10 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void {...@@ -139,7 +139,10 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void {
139}139}
140140
141pub const Mode = enum { zig, zon };141pub const Mode = enum { zig, zon };
142pub const ParseOptions = struct { recover: bool = true, mode: Mode = .zig, use_old_precedence: bool = false };142pub const ParseOptions = struct {
143 recover: bool = true,
144 mode: Mode = .zig,
145};
143146
144/// Result should be freed with tree.deinit() when there are147/// Result should be freed with tree.deinit() when there are
145/// no more references to any of the tokens or nodes.148/// no more references to any of the tokens or nodes.
...@@ -182,7 +185,6 @@ pub fn parseTokens(...@@ -182,7 +185,6 @@ pub fn parseTokens(
182 .scratch = .empty,185 .scratch = .empty,
183 .tok_i = 0,186 .tok_i = 0,
184 .recover = options.recover,187 .recover = options.recover,
185 .use_old_precedence = options.use_old_precedence,
186 };188 };
187 defer parser.errors.deinit(gpa);189 defer parser.errors.deinit(gpa);
188 defer parser.nodes.deinit(gpa);190 defer parser.nodes.deinit(gpa);
lib/std/zig/Ast/Render.zig+27-35
...@@ -4,7 +4,6 @@ const mem = std.mem;...@@ -4,7 +4,6 @@ const mem = std.mem;
4const Allocator = std.mem.Allocator;4const Allocator = std.mem.Allocator;
5const meta = std.meta;5const meta = std.meta;
6const Ast = std.zig.Ast;6const Ast = std.zig.Ast;
7const Parse = @import("../../zig/Parse.zig");
8const Token = std.zig.Token;7const Token = std.zig.Token;
9const primitives = std.zig.primitives;8const primitives = std.zig.primitives;
10const Writer = std.Io.Writer;9const Writer = std.Io.Writer;
...@@ -46,6 +45,8 @@ pub const Fixups = struct {...@@ -46,6 +45,8 @@ pub const Fixups = struct {
46 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,45 replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty,
47 /// Change all identifier names matching the key to be value instead.46 /// Change all identifier names matching the key to be value instead.
48 rename_identifiers: std.array_hash_map.String([]const u8) = .empty,47 rename_identifiers: std.array_hash_map.String([]const u8) = .empty,
48 /// Add parentheses surrounding the node.
49 insert_parentheses: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty,
4950
50 /// All `@import` builtin calls which refer to a file path will be prefixed51 /// All `@import` builtin calls which refer to a file path will be prefixed
51 /// with this path.52 /// with this path.
...@@ -59,6 +60,7 @@ pub const Fixups = struct {...@@ -59,6 +60,7 @@ pub const Fixups = struct {
59 f.append_string_after_node.count() +60 f.append_string_after_node.count() +
60 f.replace_nodes_with_node.count() +61 f.replace_nodes_with_node.count() +
61 f.rename_identifiers.count() +62 f.rename_identifiers.count() +
63 f.insert_parentheses.count() +
62 @intFromBool(f.rebase_imported_paths != null);64 @intFromBool(f.rebase_imported_paths != null);
63 }65 }
6466
...@@ -70,6 +72,7 @@ pub const Fixups = struct {...@@ -70,6 +72,7 @@ pub const Fixups = struct {
70 f.append_string_after_node.clearRetainingCapacity();72 f.append_string_after_node.clearRetainingCapacity();
71 f.replace_nodes_with_node.clearRetainingCapacity();73 f.replace_nodes_with_node.clearRetainingCapacity();
72 f.rename_identifiers.clearRetainingCapacity();74 f.rename_identifiers.clearRetainingCapacity();
75 f.insert_parentheses.clearRetainingCapacity();
7376
74 f.rebase_imported_paths = null;77 f.rebase_imported_paths = null;
75 }78 }
...@@ -82,6 +85,7 @@ pub const Fixups = struct {...@@ -82,6 +85,7 @@ pub const Fixups = struct {
82 f.append_string_after_node.deinit(gpa);85 f.append_string_after_node.deinit(gpa);
83 f.replace_nodes_with_node.deinit(gpa);86 f.replace_nodes_with_node.deinit(gpa);
84 f.rename_identifiers.deinit(gpa);87 f.rename_identifiers.deinit(gpa);
88 f.insert_parentheses.deinit(gpa);
85 f.* = undefined;89 f.* = undefined;
86 }90 }
87};91};
...@@ -421,6 +425,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {...@@ -421,6 +425,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
421 tree.tokenTag(fallback_first) == .multiline_string_literal_line;425 tree.tokenTag(fallback_first) == .multiline_string_literal_line;
422 const after_op_space: Space = if (seperate_line) .newline else .space;426 const after_op_space: Space = if (seperate_line) .newline else .space;
423427
428 if (r.fixups.insert_parentheses.contains(node)) {
429 try ais.writeByte('(');
430 }
424 try renderExpression(r, lhs, .space); // target431 try renderExpression(r, lhs, .space); // target
425432
426 try ais.pushIndent(.normal);433 try ais.pushIndent(.normal);
...@@ -433,7 +440,13 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {...@@ -433,7 +440,13 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
433 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);440 assert(tree.tokenTag(fallback_first - 1) == .keyword_catch);
434 try renderToken(r, main_token, after_op_space); // catch keyword441 try renderToken(r, main_token, after_op_space); // catch keyword
435 }442 }
436 try renderExpression(r, rhs, space); // fallback443 if (r.fixups.insert_parentheses.contains(node)) {
444 try renderExpression(r, rhs, .none);
445 try ais.writeByte(')');
446 try renderOnlySpace(r, space);
447 } else {
448 try renderExpression(r, rhs, space); // fallback
449 }
437 ais.popIndent();450 ais.popIndent();
438 },451 },
439452
...@@ -540,43 +553,22 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {...@@ -540,43 +553,22 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
540 .@"orelse",553 .@"orelse",
541 => {554 => {
542 const lhs, const rhs = tree.nodeData(node).node_and_node;555 const lhs, const rhs = tree.nodeData(node).node_and_node;
543556 if (r.fixups.insert_parentheses.contains(node)) {
544 const lhs_tag = tree.nodeTag(lhs);557 try ais.writeByte('(');
545 const center_tag = tree.nodeTag(node);558 }
546 const rhs_tag = tree.nodeTag(rhs);559 try renderExpression(r, lhs, .space);
547
548 const lhs_prec = Parse.precedenceFromNodeTag(lhs_tag);
549 const center_prec = Parse.precedenceFromNodeTag(center_tag);
550 const rhs_prec = Parse.precedenceFromNodeTag(rhs_tag);
551
552 const left_rel = lhs_prec.cmp(center_prec);
553 const right_rel = rhs_prec.cmp(center_prec);
554
555 const paren_left = left_rel == null or left_rel == .lt or (left_rel == .eq and lhs_prec.group == center_prec.group and lhs_prec.assoc == .none);
556 const paren_right = right_rel == null or right_rel == .lt;
557
558
559 if (paren_left) try ais.writeByte('(');
560 try renderExpression(r, lhs, .none);
561 if (paren_left) try ais.writeByte(')');
562 // Multiline string literals will eat the space so we need to skip if we thats next.
563 if (tree.tokenTag(tree.lastToken(lhs)) != .multiline_string_literal_line) try renderOnlySpace(r, .space);
564
565 try ais.pushIndent(.binop);
566 const op_token = tree.nodeMainToken(node);560 const op_token = tree.nodeMainToken(node);
561 try ais.pushIndent(.binop);
567 const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or562 const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or
568 tree.tokenTag(op_token + 1) == .multiline_string_literal_line;563 tree.tokenTag(op_token + 1) == .multiline_string_literal_line;
569 try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space);564 try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space);
570565 if (r.fixups.insert_parentheses.contains(node)) {
571 if (paren_right) try ais.writeByte('(');566 try renderExpression(r, rhs, .none);
572 try renderExpression(r, rhs, .none);567 try ais.writeByte(')');
573 if (paren_right) try ais.writeByte(')');568 try renderOnlySpace(r, space);
574569 } else {
575 const most_recent = tree.lastToken(rhs);570 try renderExpression(r, rhs, space);
576 const lexeme = tokenSliceForRender(tree, most_recent);571 }
577 // If our space is .none, then the above code functions as normal and putting this here will duplicate comments and such
578 if (space != .none) try renderSpace(r, most_recent, lexeme.len, space);
579
580 ais.popIndent();572 ais.popIndent();
581 },573 },
582574
lib/std/zig/Parse.zig+4-6
...@@ -24,7 +24,6 @@ nodes: Ast.NodeList,...@@ -24,7 +24,6 @@ nodes: Ast.NodeList,
24extra_data: std.ArrayList(u32),24extra_data: std.ArrayList(u32),
25scratch: std.ArrayList(Node.Index),25scratch: std.ArrayList(Node.Index),
26recover: bool,26recover: bool,
27use_old_precedence: bool = false,
2827
29fn tokenTag(p: *const Parse, token_index: TokenIndex) Token.Tag {28fn tokenTag(p: *const Parse, token_index: TokenIndex) Token.Tag {
30 return p.tokens.items(.tag)[token_index];29 return p.tokens.items(.tag)[token_index];
...@@ -1793,8 +1792,7 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32...@@ -1793,8 +1792,7 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
1793 const tok_tag = p.tokenTag(p.tok_i);1792 const tok_tag = p.tokenTag(p.tok_i);
1794 const info = operTable[@backingInt(tok_tag)];1793 const info = operTable[@backingInt(tok_tag)];
17951794
1796 const prec_relation = if (!p.use_old_precedence) info.prec.cmp(min_exc_prec) else info.prec.v16_cmp(min_exc_prec);1795 const rel = info.prec.cmp(min_exc_prec) orelse b: {
1797 const rel = prec_relation orelse b: {
1798 // This issues warnings if the relation is null, which it never is in 0.16.01796 // This issues warnings if the relation is null, which it never is in 0.16.0
1799 const prev_op_tok_tag = p.tokenTag(prev_operator_token);1797 const prev_op_tok_tag = p.tokenTag(prev_operator_token);
1800 const prev_op_info = operTable[@backingInt(prev_op_tok_tag)];1798 const prev_op_info = operTable[@backingInt(prev_op_tok_tag)];
...@@ -1831,7 +1829,8 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32...@@ -1831,7 +1829,8 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
1831 },1829 },
1832 });1830 });
1833 }1831 }
1834 break :b .gt;1832 // Use the old precedence rules to construct the tree to ease automatic fixups
1833 break :b info.prec.v16_cmp(min_exc_prec);
1835 };1834 };
18361835
1837 if (rel == .lt) {1836 if (rel == .lt) {
...@@ -1842,8 +1841,7 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32...@@ -1842,8 +1841,7 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
1842 //Special cased check to maintain the old more helpful error for chained comparisons.1841 //Special cased check to maintain the old more helpful error for chained comparisons.
1843 if (info.prec.group == .comparison) {1842 if (info.prec.group == .comparison) {
1844 try p.warn(.chained_comparison_operators);1843 try p.warn(.chained_comparison_operators);
1845 // In 0.16.0 the only reptition prohibited was comparisons which we checked above1844 } else {
1846 } else if (!p.use_old_precedence) {
1847 try p.warnMsg(.{1845 try p.warnMsg(.{
1848 .tag = .illegal_chained_operators,1846 .tag = .illegal_chained_operators,
1849 .is_note = false,1847 .is_note = false,
src/fmt.zig+81-2
...@@ -387,9 +387,88 @@ fn fmtPathFile(...@@ -387,9 +387,88 @@ fn fmtPathFile(
387 break :mode .zig;387 break :mode .zig;
388 };388 };
389389
390 var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode, .use_old_precedence = fmt.fix_precedence });390 var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode });
391 defer tree.deinit(gpa);391 defer tree.deinit(gpa);
392392
393 var fixups: std.zig.Ast.Render.Fixups = .{};
394 defer fixups.deinit(gpa);
395
396 fix: {
397 if (!fmt.fix_precedence) break :fix;
398
399 var operators_to_fix: std.AutoHashMapUnmanaged(std.zig.Ast.TokenIndex, void) = .empty;
400 defer operators_to_fix.deinit(gpa);
401
402 // Gather ambiguous operator precedence error tokens for quick lookup later
403 for (tree.errors) |err| {
404 switch (err.tag) {
405 .hint_operator_precedence_left => {
406 try operators_to_fix.putNoClobber(gpa, err.token - @as(u32, @intCast(err.extra.offset)), {});
407 },
408 .hint_operator_precedence_right => {
409 try operators_to_fix.putNoClobber(gpa, err.token, {});
410 },
411 else => continue,
412 }
413 }
414
415 if (operators_to_fix.count() == 0) break :fix;
416
417 // Build a new error list without the ambiguous operator precedence errors/notes
418 var fixed_errors: std.ArrayList(std.zig.Ast.Error) = try .initCapacity(gpa, tree.errors.len - (operators_to_fix.count() * 3));
419 for (tree.errors) |err| {
420 switch (err.tag) {
421 .ambiguous_operator_precedence,
422 .illegal_chained_operators,
423 .previous_ambiguous_operator,
424 .hint_operator_precedence_left,
425 .hint_operator_precedence_right,
426 => continue,
427 else => {
428 fixed_errors.appendAssumeCapacity(err);
429 },
430 }
431 }
432 gpa.free(tree.errors);
433 tree.errors = fixed_errors.toOwnedSliceAssert();
434
435 // Find the nodes we need to add parens to and add them to the list
436 for (tree.nodes.items(.tag), 0..) |tag, i| {
437 switch (tag) {
438 .merge_error_sets,
439 .mul,
440 .div,
441 .mod,
442 .mul_wrap,
443 .mul_sat,
444 .add,
445 .sub,
446 .array_cat,
447 .add_wrap,
448 .sub_wrap,
449 .add_sat,
450 .sub_sat,
451 .shl,
452 .shl_sat,
453 .shr,
454 .bit_and,
455 .bit_xor,
456 .bit_or,
457 .@"orelse",
458 .@"catch",
459 .bool_and,
460 .bool_or,
461 => {
462 const main_tok = tree.nodes.get(i).main_token;
463 if (operators_to_fix.contains(main_tok)) {
464 try fixups.insert_parentheses.put(gpa, @fromBackingInt(@intCast(i)), {});
465 }
466 },
467 else => {},
468 }
469 }
470 }
471
393 if (tree.errors.len != 0) {472 if (tree.errors.len != 0) {
394 try std.zig.printAstErrorsToStderr(gpa, io, tree, file_path, fmt.color);473 try std.zig.printAstErrorsToStderr(gpa, io, tree, file_path, fmt.color);
395 fmt.any_error = true;474 fmt.any_error = true;
...@@ -497,7 +576,7 @@ fn fmtPathFile(...@@ -497,7 +576,7 @@ fn fmtPathFile(
497 fmt.out_buffer.clearRetainingCapacity();576 fmt.out_buffer.clearRetainingCapacity();
498 try fmt.out_buffer.ensureTotalCapacity(source_code.len);577 try fmt.out_buffer.ensureTotalCapacity(source_code.len);
499578
500 tree.render(gpa, &fmt.out_buffer.writer, .{}) catch |err| switch (err) {579 tree.render(gpa, &fmt.out_buffer.writer, fixups) catch |err| switch (err) {
501 error.WriteFailed, error.OutOfMemory => return error.OutOfMemory,580 error.WriteFailed, error.OutOfMemory => return error.OutOfMemory,
502 };581 };
503 if (!mem.eql(u8, fmt.out_buffer.written(), source_code)) {582 if (!mem.eql(u8, fmt.out_buffer.written(), source_code)) {