authorgravatar for gbell12907@gmail.comCMDRZero <gbell12907@gmail.com> 2026-07-18 23:57:30-05:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-10-22 12:21:43+02:00
logc57dfa2f06e088b67ace125f84e2d97de5396c41
tree72ce9b61000c17cf3b74fd252aabd08959d22a75
parent1bae04f02026427d1754f704bd24fce3317f3e89

Add --upgrade for migrating precedence. Known bug: deletes comments from assignment lines. No clue why.


4 files changed, 105 insertions(+), 36 deletions(-)

lib/std/zig/Ast.zig+2-4
......@@ -139,10 +139,7 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void {
139139}
140140
141141pub const Mode = enum { zig, zon };
142pub const ParseOptions = struct {
143 recover: bool = true,
144 mode: Mode = .zig,
145};
142pub const ParseOptions = struct { recover: bool = true, mode: Mode = .zig, use_old_precedence: bool = false };
146143
147144/// Result should be freed with tree.deinit() when there are
148145/// no more references to any of the tokens or nodes.
......@@ -185,6 +182,7 @@ pub fn parseTokens(
185182 .scratch = .empty,
186183 .tok_i = 0,
187184 .recover = options.recover,
185 .use_old_precedence = options.use_old_precedence,
188186 };
189187 defer parser.errors.deinit(gpa);
190188 defer parser.nodes.deinit(gpa);
lib/std/zig/Ast/Render.zig+34-3
......@@ -4,6 +4,7 @@ const mem = std.mem;
44const Allocator = std.mem.Allocator;
55const meta = std.meta;
66const Ast = std.zig.Ast;
7const Parse = @import("../../zig/Parse.zig");
78const Token = std.zig.Token;
89const primitives = std.zig.primitives;
910const Writer = std.Io.Writer;
......@@ -539,13 +540,43 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void {
539540 .@"orelse",
540541 => {
541542 const lhs, const rhs = tree.nodeData(node).node_and_node;
542 try renderExpression(r, lhs, .space);
543 const op_token = tree.nodeMainToken(node);
543
544 const lhs_tag = tree.nodeTag(lhs);
545 const center_tag = tree.nodeTag(node);
546 const rhs_tag = tree.nodeTag(rhs);
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
544565 try ais.pushIndent(.binop);
566 const op_token = tree.nodeMainToken(node);
545567 const rhs_seperate_line = !tree.tokensOnSameLine(op_token, op_token + 1) or
546568 tree.tokenTag(op_token + 1) == .multiline_string_literal_line;
547569 try renderToken(r, op_token, if (rhs_seperate_line) .newline else .space);
548 try renderExpression(r, rhs, space);
570
571 if (paren_right) try ais.writeByte('(');
572 try renderExpression(r, rhs, .none);
573 if (paren_right) try ais.writeByte(')');
574
575 const most_recent = tree.lastToken(rhs);
576 const lexeme = tokenSliceForRender(tree, most_recent);
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
549580 ais.popIndent();
550581 },
551582
lib/std/zig/Parse.zig+63-28
......@@ -24,6 +24,7 @@ nodes: Ast.NodeList,
2424extra_data: std.ArrayList(u32),
2525scratch: std.ArrayList(Node.Index),
2626recover: bool,
27use_old_precedence: bool = false,
2728
2829fn tokenTag(p: *const Parse, token_index: TokenIndex) Token.Tag {
2930 return p.tokens.items(.tag)[token_index];
......@@ -1531,6 +1532,8 @@ const PrecClass = struct {
15311532 const Group = enum {
15321533 const num_distinct = std.meta.fieldNames(Group).len;
15331534
1535 top,
1536
15341537 arithmetic_product_chainable,
15351538 arithmetic_mod_nonchainable,
15361539 arithmetic_sum,
......@@ -1588,7 +1591,7 @@ const PrecClass = struct {
15881591 gt = 1,
15891592
15901593 fn inverted(self: Rel) Rel {
1591 return @enumFromInt(-@intFromEnum(self));
1594 return @fromBackingInt(@intCast(-@backingInt(self)));
15921595 }
15931596 };
15941597
......@@ -1602,12 +1605,14 @@ const PrecClass = struct {
16021605 if (!std.mem.startsWith(u8, fieldname_j, @tagName(major_b))) continue;
16031606
16041607 prec_array.*[i][j] = rel;
1608 prec_array.*[j][i] = rel.inverted();
16051609 }
16061610 }
16071611 }
16081612
16091613 fn setMinorRelation(prec_array: *PrecedenceArray, minor_a: Group, rel: Rel, minor_b: Group) void {
1610 prec_array.*[@intFromEnum(minor_a)][@intFromEnum(minor_b)] = rel;
1614 prec_array.*[@backingInt(minor_a)][@backingInt(minor_b)] = rel;
1615 prec_array.*[@backingInt(minor_b)][@backingInt(minor_a)] = rel.inverted();
16111616 }
16121617
16131618 /// Arithmetic_* > Comparison
......@@ -1622,6 +1627,9 @@ const PrecClass = struct {
16221627 @setEvalBranchQuota(20_000);
16231628 const major_greater_than_relations = [_][2]Group.Major{
16241629 // zig fmt: off
1630 .{ .top, .arithmetic },
1631 .{ .top, .bitwise },
1632 .{ .top, .recovery },
16251633 .{ .arithmetic, .comparison },
16261634 .{ .bitwise, .comparison },
16271635 .{ .comparison, .bool },
......@@ -1656,30 +1664,21 @@ const PrecClass = struct {
16561664
16571665 var order: PrecedenceArray = @splat(@splat(null));
16581666
1667 setMinorRelation(&order, .top, .gt, .arithmetic_product_chainable);
1668
16591669 setMinorRelation(&order, .arithmetic_product_chainable, .eq, .arithmetic_mod_nonchainable);
16601670 setMinorRelation(&order, .arithmetic_mod_nonchainable, .gt, .arithmetic_sum);
1661 setMinorRelation(&order, .arithmetic_product_chainable, .gt, .arithmetic_sum);
16621671
16631672 setMinorRelation(&order, .arithmetic_sum, .gt, .bitwise_shift);
16641673
1665 // Here since the transative ordering computer cant leverage equals to derive a = b < c --> a < c, we have to manually list these
16661674 setMinorRelation(&order, .bitwise_shift, .gt, .bitwise_and);
1667 setMinorRelation(&order, .bitwise_shift, .gt, .bitwise_xor);
1668 setMinorRelation(&order, .bitwise_shift, .gt, .bitwise_or);
1669 setMinorRelation(&order, .bitwise_shift, .gt, .recovery_catch);
1670 setMinorRelation(&order, .bitwise_shift, .gt, .recovery_orelse);
16711675
16721676 setMinorRelation(&order, .bitwise_and, .eq, .bitwise_xor);
1673 setMinorRelation(&order, .bitwise_xor, .eq, .bitwise_or);
1674 setMinorRelation(&order, .bitwise_or, .eq, .recovery_catch);
1675 setMinorRelation(&order, .recovery_catch, .eq, .recovery_orelse);
1676 setMinorRelation(&order, .recovery_orelse, .eq, .bitwise_and);
1677 setMinorRelation(&order, .bitwise_and, .eq, .bitwise_or);
1678 setMinorRelation(&order, .bitwise_and, .eq, .recovery_catch);
1679 setMinorRelation(&order, .bitwise_and, .eq, .recovery_orelse);
16771680
16781681 setMinorRelation(&order, .bitwise_and, .gt, .comparison);
1679 setMinorRelation(&order, .bitwise_xor, .gt, .comparison);
1680 setMinorRelation(&order, .bitwise_or, .gt, .comparison);
1681 setMinorRelation(&order, .recovery_catch, .gt, .comparison);
1682 setMinorRelation(&order, .recovery_orelse, .gt, .comparison);
16831682
16841683 setMinorRelation(&order, .comparison, .gt, .bool_and);
16851684
......@@ -1697,16 +1696,22 @@ const PrecClass = struct {
16971696 };
16981697
16991698 pub fn cmp(lhs: PrecClass, rhs: PrecClass) ?Rel {
1700 return PrecClass.precedence_ordering[@intFromEnum(lhs.group)][@intFromEnum(rhs.group)];
1699 return PrecClass.precedence_ordering[@backingInt(lhs.group)][@backingInt(rhs.group)];
17011700 }
17021701
17031702 pub fn v16_cmp(lhs: PrecClass, rhs: PrecClass) Rel {
1704 return PrecClass.v16_precedence_ordering[@intFromEnum(lhs.group)][@intFromEnum(rhs.group)].?;
1703 return PrecClass.v16_precedence_ordering[@backingInt(lhs.group)][@backingInt(rhs.group)].?;
17051704 }
17061705
17071706 fn computeTransativeOrdering(order: *PrecedenceArray) void {
17081707 for (0..Group.num_distinct) |i| for (0..Group.num_distinct) |j| for (0..Group.num_distinct) |k| {
1709 if (order[i][k] == order[k][j] and order[i][k] != null) order[i][j] = order[i][k];
1708 if (order[i][j] == null or order[j][k] == null) continue;
1709 // X < Y < Z ==> X < Z X > Y > Z ==> X > Z
1710 if (order[i][j] == order[j][k]) order[i][k] = order[i][j];
1711 // X = Y < Z ==> X < Z X = Y > Z ==> X > Z
1712 if (order[i][j] == .eq) order[i][k] = order[j][k];
1713 // X < Y = Z ==> X < Z X > Y = Z ==> X > Z
1714 if (order[j][k] == .eq) order[i][k] = order[i][j];
17101715 };
17111716 for (0..Group.num_distinct) |i| for (0..Group.num_distinct) |j| {
17121717 if (i == j) order[i][j] = .eq;
......@@ -1762,6 +1767,23 @@ const operTable = std.enums.directEnumArrayDefault(Token.Tag, OperInfo, .{ .prec
17621767 // zig fmt: on
17631768});
17641769
1770fn comptimePrecedenceFromNodeTag(comptime tag: Node.Tag) PrecClass {
1771 for (operTable) |data| {
1772 if (data.tag == tag) return data.prec;
1773 }
1774 return .{ .group = .top, .assoc = .left };
1775}
1776
1777pub fn precedenceFromNodeTag(tag: Node.Tag) PrecClass {
1778 return switch (tag) {
1779 inline else => |ct_tag| comptimePrecedenceFromNodeTag(ct_tag),
1780 };
1781}
1782
1783pub fn newPrecedenceBetween(left_tok: Node.Tag, right_tok: Node.Tag) ?PrecClass.Rel {
1784 return precedenceFromNodeTag(left_tok).cmp(precedenceFromNodeTag(right_tok));
1785}
1786
17651787fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32) Error!?Node.Index {
17661788 var node = try p.parsePrefixExpr() orelse return null;
17671789
......@@ -1769,17 +1791,21 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
17691791
17701792 while (true) {
17711793 const tok_tag = p.tokenTag(p.tok_i);
1772 const info = operTable[@intFromEnum(tok_tag)];
1794 const info = operTable[@backingInt(tok_tag)];
17731795
1774 const rel = info.prec.cmp(min_exc_prec) orelse b: {
1796 const prec_relation = if (!p.use_old_precedence) info.prec.cmp(min_exc_prec) else info.prec.v16_cmp(min_exc_prec);
1797 const rel = prec_relation orelse b: {
1798 // This issues warnings if the relation is null, which it never is in 0.16.0
17751799 const prev_op_tok_tag = p.tokenTag(prev_operator_token);
1776 const prev_op_info = operTable[@intFromEnum(prev_op_tok_tag)];
1800 const prev_op_info = operTable[@backingInt(prev_op_tok_tag)];
17771801 const v16_rel = prev_op_info.prec.v16_cmp(info.prec);
17781802 try p.warnMsg(.{
17791803 .tag = .ambiguous_operator_precedence,
17801804 .is_note = false,
17811805 .token = p.tok_i,
1782 .extra = .{ .offset = p.tok_i - prev_operator_token },
1806 .extra = .{
1807 .offset = p.tok_i - prev_operator_token,
1808 },
17831809 });
17841810 try p.warnMsg(.{
17851811 .tag = .previous_ambiguous_operator,
......@@ -1791,14 +1817,18 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
17911817 .tag = .hint_operator_precedence_left,
17921818 .is_note = true,
17931819 .token = p.tok_i,
1794 .extra = .{ .offset = p.tok_i - prev_operator_token },
1820 .extra = .{
1821 .offset = p.tok_i - prev_operator_token,
1822 },
17951823 });
17961824 } else {
17971825 try p.warnMsg(.{
17981826 .tag = .hint_operator_precedence_right,
17991827 .is_note = true,
18001828 .token = p.tok_i,
1801 .extra = .{ .offset = p.tok_i - prev_operator_token },
1829 .extra = .{
1830 .offset = p.tok_i - prev_operator_token,
1831 },
18021832 });
18031833 }
18041834 break :b .gt;
......@@ -1812,12 +1842,15 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
18121842 //Special cased check to maintain the old more helpful error for chained comparisons.
18131843 if (info.prec.group == .comparison) {
18141844 try p.warn(.chained_comparison_operators);
1815 } else {
1845 // In 0.16.0 the only reptition prohibited was comparisons which we checked above
1846 } else if (!p.use_old_precedence) {
18161847 try p.warnMsg(.{
18171848 .tag = .illegal_chained_operators,
18181849 .is_note = false,
18191850 .token = p.tok_i,
1820 .extra = .{ .offset = p.tok_i - prev_operator_token },
1851 .extra = .{
1852 .offset = p.tok_i - prev_operator_token,
1853 },
18211854 });
18221855 try p.warnMsg(.{
18231856 .tag = .previous_ambiguous_operator,
......@@ -1828,7 +1861,9 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32
18281861 .tag = .hint_operator_precedence_left,
18291862 .is_note = true,
18301863 .token = p.tok_i,
1831 .extra = .{ .offset = p.tok_i - prev_operator_token },
1864 .extra = .{
1865 .offset = p.tok_i - prev_operator_token,
1866 },
18321867 });
18331868 }
18341869 }
src/fmt.zig+6-1
......@@ -41,6 +41,7 @@ const Fmt = struct {
4141 out_buffer: std.Io.Writer.Allocating,
4242 stdout_writer: *Io.File.Writer,
4343 stats: ?*Stats,
44 fix_precedence: bool,
4445
4546 const SeenMap = std.AutoHashMap(Io.File.INode, void);
4647};
......@@ -66,6 +67,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
6667 var check_ast_flag = false;
6768 var force_zon = false;
6869 var enable_stats = false;
70 var fix_precedence = false;
6971
7072 var input_files = std.array_list.Managed([]const u8).init(gpa);
7173 defer input_files.deinit();
......@@ -107,6 +109,8 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
107109 try excluded_files.append(next_arg);
108110 } else if (mem.eql(u8, arg, "--zon")) {
109111 force_zon = true;
112 } else if (mem.eql(u8, arg, "--upgrade")) {
113 fix_precedence = true;
110114 } else {
111115 fatal("unrecognized parameter: {q}", .{arg});
112116 }
......@@ -198,6 +202,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) !
198202 .out_buffer = .init(gpa),
199203 .stdout_writer = &stdout_writer,
200204 .stats = null,
205 .fix_precedence = fix_precedence,
201206 };
202207 defer fmt.seen.deinit();
203208 defer fmt.out_buffer.deinit();
......@@ -382,7 +387,7 @@ fn fmtPathFile(
382387 break :mode .zig;
383388 };
384389
385 var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode });
390 var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode, .use_old_precedence = fmt.fix_precedence });
386391 defer tree.deinit(gpa);
387392
388393 if (tree.errors.len != 0) {