From c57dfa2f06e088b67ace125f84e2d97de5396c41 Mon Sep 17 00:00:00 2001 From: CMDRZero Date: Sat, 18 Jul 2026 23:57:30 -0500 Subject: [PATCH] Add --upgrade for migrating precedence. Known bug: deletes comments from assignment lines. No clue why. --- lib/std/zig/Ast.zig | 6 +-- lib/std/zig/Ast/Render.zig | 37 ++++++++++++++-- lib/std/zig/Parse.zig | 91 ++++++++++++++++++++++++++------------ src/fmt.zig | 7 ++- 4 files changed, 105 insertions(+), 36 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 14a16e1730564aa7add9e659dc81a76175b5a95c..7a23a1653f9d68803c9b6689d293aecae0c55993 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -139,10 +139,7 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void { } pub const Mode = enum { zig, zon }; -pub const ParseOptions = struct { - recover: bool = true, - mode: Mode = .zig, -}; +pub const ParseOptions = struct { recover: bool = true, mode: Mode = .zig, use_old_precedence: bool = false }; /// Result should be freed with tree.deinit() when there are /// no more references to any of the tokens or nodes. @@ -185,6 +182,7 @@ pub fn parseTokens( .scratch = .empty, .tok_i = 0, .recover = options.recover, + .use_old_precedence = options.use_old_precedence, }; defer parser.errors.deinit(gpa); defer parser.nodes.deinit(gpa); diff --git a/lib/std/zig/Ast/Render.zig b/lib/std/zig/Ast/Render.zig index f4cfed386044725ea7074d584d1879ac42b33777..5a111162cd63ee50209e713dcab954f103b0b009 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -4,6 +4,7 @@ const mem = std.mem; const Allocator = std.mem.Allocator; const meta = std.meta; const Ast = std.zig.Ast; +const Parse = @import("../../zig/Parse.zig"); const Token = std.zig.Token; const primitives = std.zig.primitives; const Writer = std.Io.Writer; @@ -539,13 +540,43 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { .@"orelse", => { const lhs, const rhs = tree.nodeData(node).node_and_node; - try renderExpression(r, lhs, .space); - const op_token = tree.nodeMainToken(node); + + const lhs_tag = tree.nodeTag(lhs); + const center_tag = tree.nodeTag(node); + const rhs_tag = tree.nodeTag(rhs); + + const lhs_prec = Parse.precedenceFromNodeTag(lhs_tag); + const center_prec = Parse.precedenceFromNodeTag(center_tag); + const rhs_prec = Parse.precedenceFromNodeTag(rhs_tag); + + const left_rel = lhs_prec.cmp(center_prec); + const right_rel = rhs_prec.cmp(center_prec); + + 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); + const paren_right = right_rel == null or right_rel == .lt; + + + if (paren_left) try ais.writeByte('('); + try renderExpression(r, lhs, .none); + if (paren_left) try ais.writeByte(')'); + // Multiline string literals will eat the space so we need to skip if we thats next. + if (tree.tokenTag(tree.lastToken(lhs)) != .multiline_string_literal_line) try renderOnlySpace(r, .space); + try ais.pushIndent(.binop); + const op_token = tree.nodeMainToken(node); 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); + + if (paren_right) try ais.writeByte('('); + try renderExpression(r, rhs, .none); + if (paren_right) try ais.writeByte(')'); + + const most_recent = tree.lastToken(rhs); + const lexeme = tokenSliceForRender(tree, most_recent); + // If our space is .none, then the above code functions as normal and putting this here will duplicate comments and such + if (space != .none) try renderSpace(r, most_recent, lexeme.len, space); + ais.popIndent(); }, diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index 79e90e487284339b5a438e15bbdb8fc40b9a27ef..ff6a74821ac46dfefb5a64e84cffcc23aa6f5dc3 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -24,6 +24,7 @@ nodes: Ast.NodeList, extra_data: std.ArrayList(u32), scratch: std.ArrayList(Node.Index), recover: bool, +use_old_precedence: bool = false, fn tokenTag(p: *const Parse, token_index: TokenIndex) Token.Tag { return p.tokens.items(.tag)[token_index]; @@ -1531,6 +1532,8 @@ const PrecClass = struct { const Group = enum { const num_distinct = std.meta.fieldNames(Group).len; + top, + arithmetic_product_chainable, arithmetic_mod_nonchainable, arithmetic_sum, @@ -1588,7 +1591,7 @@ const PrecClass = struct { gt = 1, fn inverted(self: Rel) Rel { - return @enumFromInt(-@intFromEnum(self)); + return @fromBackingInt(@intCast(-@backingInt(self))); } }; @@ -1602,12 +1605,14 @@ const PrecClass = struct { if (!std.mem.startsWith(u8, fieldname_j, @tagName(major_b))) continue; prec_array.*[i][j] = rel; + prec_array.*[j][i] = rel.inverted(); } } } fn setMinorRelation(prec_array: *PrecedenceArray, minor_a: Group, rel: Rel, minor_b: Group) void { - prec_array.*[@intFromEnum(minor_a)][@intFromEnum(minor_b)] = rel; + prec_array.*[@backingInt(minor_a)][@backingInt(minor_b)] = rel; + prec_array.*[@backingInt(minor_b)][@backingInt(minor_a)] = rel.inverted(); } /// Arithmetic_* > Comparison @@ -1622,6 +1627,9 @@ const PrecClass = struct { @setEvalBranchQuota(20_000); const major_greater_than_relations = [_][2]Group.Major{ // zig fmt: off + .{ .top, .arithmetic }, + .{ .top, .bitwise }, + .{ .top, .recovery }, .{ .arithmetic, .comparison }, .{ .bitwise, .comparison }, .{ .comparison, .bool }, @@ -1656,30 +1664,21 @@ const PrecClass = struct { var order: PrecedenceArray = @splat(@splat(null)); + setMinorRelation(&order, .top, .gt, .arithmetic_product_chainable); + setMinorRelation(&order, .arithmetic_product_chainable, .eq, .arithmetic_mod_nonchainable); setMinorRelation(&order, .arithmetic_mod_nonchainable, .gt, .arithmetic_sum); - setMinorRelation(&order, .arithmetic_product_chainable, .gt, .arithmetic_sum); setMinorRelation(&order, .arithmetic_sum, .gt, .bitwise_shift); - // Here since the transative ordering computer cant leverage equals to derive a = b < c --> a < c, we have to manually list these setMinorRelation(&order, .bitwise_shift, .gt, .bitwise_and); - setMinorRelation(&order, .bitwise_shift, .gt, .bitwise_xor); - setMinorRelation(&order, .bitwise_shift, .gt, .bitwise_or); - setMinorRelation(&order, .bitwise_shift, .gt, .recovery_catch); - setMinorRelation(&order, .bitwise_shift, .gt, .recovery_orelse); setMinorRelation(&order, .bitwise_and, .eq, .bitwise_xor); - setMinorRelation(&order, .bitwise_xor, .eq, .bitwise_or); - setMinorRelation(&order, .bitwise_or, .eq, .recovery_catch); - setMinorRelation(&order, .recovery_catch, .eq, .recovery_orelse); - setMinorRelation(&order, .recovery_orelse, .eq, .bitwise_and); + setMinorRelation(&order, .bitwise_and, .eq, .bitwise_or); + setMinorRelation(&order, .bitwise_and, .eq, .recovery_catch); + setMinorRelation(&order, .bitwise_and, .eq, .recovery_orelse); setMinorRelation(&order, .bitwise_and, .gt, .comparison); - setMinorRelation(&order, .bitwise_xor, .gt, .comparison); - setMinorRelation(&order, .bitwise_or, .gt, .comparison); - setMinorRelation(&order, .recovery_catch, .gt, .comparison); - setMinorRelation(&order, .recovery_orelse, .gt, .comparison); setMinorRelation(&order, .comparison, .gt, .bool_and); @@ -1697,16 +1696,22 @@ const PrecClass = struct { }; pub fn cmp(lhs: PrecClass, rhs: PrecClass) ?Rel { - return PrecClass.precedence_ordering[@intFromEnum(lhs.group)][@intFromEnum(rhs.group)]; + return PrecClass.precedence_ordering[@backingInt(lhs.group)][@backingInt(rhs.group)]; } pub fn v16_cmp(lhs: PrecClass, rhs: PrecClass) Rel { - return PrecClass.v16_precedence_ordering[@intFromEnum(lhs.group)][@intFromEnum(rhs.group)].?; + return PrecClass.v16_precedence_ordering[@backingInt(lhs.group)][@backingInt(rhs.group)].?; } fn computeTransativeOrdering(order: *PrecedenceArray) void { for (0..Group.num_distinct) |i| for (0..Group.num_distinct) |j| for (0..Group.num_distinct) |k| { - if (order[i][k] == order[k][j] and order[i][k] != null) order[i][j] = order[i][k]; + if (order[i][j] == null or order[j][k] == null) continue; + // X < Y < Z ==> X < Z X > Y > Z ==> X > Z + if (order[i][j] == order[j][k]) order[i][k] = order[i][j]; + // X = Y < Z ==> X < Z X = Y > Z ==> X > Z + if (order[i][j] == .eq) order[i][k] = order[j][k]; + // X < Y = Z ==> X < Z X > Y = Z ==> X > Z + if (order[j][k] == .eq) order[i][k] = order[i][j]; }; for (0..Group.num_distinct) |i| for (0..Group.num_distinct) |j| { if (i == j) order[i][j] = .eq; @@ -1762,6 +1767,23 @@ const operTable = std.enums.directEnumArrayDefault(Token.Tag, OperInfo, .{ .prec // zig fmt: on }); +fn comptimePrecedenceFromNodeTag(comptime tag: Node.Tag) PrecClass { + for (operTable) |data| { + if (data.tag == tag) return data.prec; + } + return .{ .group = .top, .assoc = .left }; +} + +pub fn precedenceFromNodeTag(tag: Node.Tag) PrecClass { + return switch (tag) { + inline else => |ct_tag| comptimePrecedenceFromNodeTag(ct_tag), + }; +} + +pub fn newPrecedenceBetween(left_tok: Node.Tag, right_tok: Node.Tag) ?PrecClass.Rel { + return precedenceFromNodeTag(left_tok).cmp(precedenceFromNodeTag(right_tok)); +} + fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32) Error!?Node.Index { var node = try p.parsePrefixExpr() orelse return null; @@ -1769,17 +1791,21 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32 while (true) { const tok_tag = p.tokenTag(p.tok_i); - const info = operTable[@intFromEnum(tok_tag)]; + const info = operTable[@backingInt(tok_tag)]; - const rel = info.prec.cmp(min_exc_prec) orelse b: { + const prec_relation = if (!p.use_old_precedence) info.prec.cmp(min_exc_prec) else info.prec.v16_cmp(min_exc_prec); + const rel = prec_relation orelse b: { + // This issues warnings if the relation is null, which it never is in 0.16.0 const prev_op_tok_tag = p.tokenTag(prev_operator_token); - const prev_op_info = operTable[@intFromEnum(prev_op_tok_tag)]; + const prev_op_info = operTable[@backingInt(prev_op_tok_tag)]; const v16_rel = prev_op_info.prec.v16_cmp(info.prec); try p.warnMsg(.{ .tag = .ambiguous_operator_precedence, .is_note = false, .token = p.tok_i, - .extra = .{ .offset = p.tok_i - prev_operator_token }, + .extra = .{ + .offset = p.tok_i - prev_operator_token, + }, }); try p.warnMsg(.{ .tag = .previous_ambiguous_operator, @@ -1791,14 +1817,18 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32 .tag = .hint_operator_precedence_left, .is_note = true, .token = p.tok_i, - .extra = .{ .offset = p.tok_i - prev_operator_token }, + .extra = .{ + .offset = p.tok_i - prev_operator_token, + }, }); } else { try p.warnMsg(.{ .tag = .hint_operator_precedence_right, .is_note = true, .token = p.tok_i, - .extra = .{ .offset = p.tok_i - prev_operator_token }, + .extra = .{ + .offset = p.tok_i - prev_operator_token, + }, }); } break :b .gt; @@ -1812,12 +1842,15 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32 //Special cased check to maintain the old more helpful error for chained comparisons. if (info.prec.group == .comparison) { try p.warn(.chained_comparison_operators); - } else { + // In 0.16.0 the only reptition prohibited was comparisons which we checked above + } else if (!p.use_old_precedence) { try p.warnMsg(.{ .tag = .illegal_chained_operators, .is_note = false, .token = p.tok_i, - .extra = .{ .offset = p.tok_i - prev_operator_token }, + .extra = .{ + .offset = p.tok_i - prev_operator_token, + }, }); try p.warnMsg(.{ .tag = .previous_ambiguous_operator, @@ -1828,7 +1861,9 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32 .tag = .hint_operator_precedence_left, .is_note = true, .token = p.tok_i, - .extra = .{ .offset = p.tok_i - prev_operator_token }, + .extra = .{ + .offset = p.tok_i - prev_operator_token, + }, }); } } diff --git a/src/fmt.zig b/src/fmt.zig index 42e7558ea9950968af8c026aa755951a052cd31e..16dfb01d8f92a733ed2068d649dffc07ca6b587d 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -41,6 +41,7 @@ const Fmt = struct { out_buffer: std.Io.Writer.Allocating, stdout_writer: *Io.File.Writer, stats: ?*Stats, + fix_precedence: bool, const SeenMap = std.AutoHashMap(Io.File.INode, void); }; @@ -66,6 +67,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! var check_ast_flag = false; var force_zon = false; var enable_stats = false; + var fix_precedence = false; var input_files = std.array_list.Managed([]const u8).init(gpa); defer input_files.deinit(); @@ -107,6 +109,8 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! try excluded_files.append(next_arg); } else if (mem.eql(u8, arg, "--zon")) { force_zon = true; + } else if (mem.eql(u8, arg, "--upgrade")) { + fix_precedence = true; } else { fatal("unrecognized parameter: {q}", .{arg}); } @@ -198,6 +202,7 @@ pub fn run(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! .out_buffer = .init(gpa), .stdout_writer = &stdout_writer, .stats = null, + .fix_precedence = fix_precedence, }; defer fmt.seen.deinit(); defer fmt.out_buffer.deinit(); @@ -382,7 +387,7 @@ fn fmtPathFile( break :mode .zig; }; - var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode }); + var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode, .use_old_precedence = fmt.fix_precedence }); defer tree.deinit(gpa); if (tree.errors.len != 0) { -- 2.54.0