diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 7a23a1653f9d68803c9b6689d293aecae0c55993..14a16e1730564aa7add9e659dc81a76175b5a95c 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -139,7 +139,10 @@ pub fn deinit(tree: *Ast, gpa: Allocator) void { } pub const Mode = enum { zig, zon }; -pub const ParseOptions = struct { recover: bool = true, mode: Mode = .zig, use_old_precedence: bool = false }; +pub const ParseOptions = struct { + recover: bool = true, + mode: Mode = .zig, +}; /// Result should be freed with tree.deinit() when there are /// no more references to any of the tokens or nodes. @@ -182,7 +185,6 @@ 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 5a111162cd63ee50209e713dcab954f103b0b009..060ce9af8d0b7673504ce0f7598c40c071d57dd8 100644 --- a/lib/std/zig/Ast/Render.zig +++ b/lib/std/zig/Ast/Render.zig @@ -4,7 +4,6 @@ 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; @@ -46,6 +45,8 @@ pub const Fixups = struct { replace_nodes_with_node: std.AutoHashMapUnmanaged(Ast.Node.Index, Ast.Node.Index) = .empty, /// Change all identifier names matching the key to be value instead. rename_identifiers: std.array_hash_map.String([]const u8) = .empty, + /// Add parentheses surrounding the node. + insert_parentheses: std.AutoHashMapUnmanaged(Ast.Node.Index, void) = .empty, /// All `@import` builtin calls which refer to a file path will be prefixed /// with this path. @@ -59,6 +60,7 @@ pub const Fixups = struct { f.append_string_after_node.count() + f.replace_nodes_with_node.count() + f.rename_identifiers.count() + + f.insert_parentheses.count() + @intFromBool(f.rebase_imported_paths != null); } @@ -70,6 +72,7 @@ pub const Fixups = struct { f.append_string_after_node.clearRetainingCapacity(); f.replace_nodes_with_node.clearRetainingCapacity(); f.rename_identifiers.clearRetainingCapacity(); + f.insert_parentheses.clearRetainingCapacity(); f.rebase_imported_paths = null; } @@ -82,6 +85,7 @@ pub const Fixups = struct { f.append_string_after_node.deinit(gpa); f.replace_nodes_with_node.deinit(gpa); f.rename_identifiers.deinit(gpa); + f.insert_parentheses.deinit(gpa); f.* = undefined; } }; @@ -421,6 +425,9 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { tree.tokenTag(fallback_first) == .multiline_string_literal_line; const after_op_space: Space = if (seperate_line) .newline else .space; + if (r.fixups.insert_parentheses.contains(node)) { + try ais.writeByte('('); + } try renderExpression(r, lhs, .space); // target try ais.pushIndent(.normal); @@ -433,7 +440,13 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { assert(tree.tokenTag(fallback_first - 1) == .keyword_catch); try renderToken(r, main_token, after_op_space); // catch keyword } - try renderExpression(r, rhs, space); // fallback + if (r.fixups.insert_parentheses.contains(node)) { + try renderExpression(r, rhs, .none); + try ais.writeByte(')'); + try renderOnlySpace(r, space); + } else { + try renderExpression(r, rhs, space); // fallback + } ais.popIndent(); }, @@ -540,43 +553,22 @@ fn renderExpression(r: *Render, node: Ast.Node.Index, space: Space) Error!void { .@"orelse", => { const lhs, const rhs = tree.nodeData(node).node_and_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); - + if (r.fixups.insert_parentheses.contains(node)) { + try ais.writeByte('('); + } + try renderExpression(r, lhs, .space); + const op_token = tree.nodeMainToken(node); 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); - - 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); - + if (r.fixups.insert_parentheses.contains(node)) { + try renderExpression(r, rhs, .none); + try ais.writeByte(')'); + try renderOnlySpace(r, space); + } else { + try renderExpression(r, rhs, space); + } ais.popIndent(); }, diff --git a/lib/std/zig/Parse.zig b/lib/std/zig/Parse.zig index ff6a74821ac46dfefb5a64e84cffcc23aa6f5dc3..64e5c398dded4cdeb52a29ba48ae78735e5b75b6 100644 --- a/lib/std/zig/Parse.zig +++ b/lib/std/zig/Parse.zig @@ -24,7 +24,6 @@ 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]; @@ -1793,8 +1792,7 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32 const tok_tag = p.tokenTag(p.tok_i); const info = operTable[@backingInt(tok_tag)]; - 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: { + const rel = info.prec.cmp(min_exc_prec) 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[@backingInt(prev_op_tok_tag)]; @@ -1831,7 +1829,8 @@ fn parseExprPrecedence(p: *Parse, min_exc_prec: PrecClass, prev_token_const: u32 }, }); } - break :b .gt; + // Use the old precedence rules to construct the tree to ease automatic fixups + break :b info.prec.v16_cmp(min_exc_prec); }; if (rel == .lt) { @@ -1842,8 +1841,7 @@ 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); - // In 0.16.0 the only reptition prohibited was comparisons which we checked above - } else if (!p.use_old_precedence) { + } else { try p.warnMsg(.{ .tag = .illegal_chained_operators, .is_note = false, diff --git a/src/fmt.zig b/src/fmt.zig index 16dfb01d8f92a733ed2068d649dffc07ca6b587d..3e104386d51054041422e596131fdc7919087058 100644 --- a/src/fmt.zig +++ b/src/fmt.zig @@ -387,9 +387,88 @@ fn fmtPathFile( break :mode .zig; }; - var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode, .use_old_precedence = fmt.fix_precedence }); + var tree = try std.zig.Ast.parse(gpa, source_code, .{ .mode = mode }); defer tree.deinit(gpa); + var fixups: std.zig.Ast.Render.Fixups = .{}; + defer fixups.deinit(gpa); + + fix: { + if (!fmt.fix_precedence) break :fix; + + var operators_to_fix: std.AutoHashMapUnmanaged(std.zig.Ast.TokenIndex, void) = .empty; + defer operators_to_fix.deinit(gpa); + + // Gather ambiguous operator precedence error tokens for quick lookup later + for (tree.errors) |err| { + switch (err.tag) { + .hint_operator_precedence_left => { + try operators_to_fix.putNoClobber(gpa, err.token - @as(u32, @intCast(err.extra.offset)), {}); + }, + .hint_operator_precedence_right => { + try operators_to_fix.putNoClobber(gpa, err.token, {}); + }, + else => continue, + } + } + + if (operators_to_fix.count() == 0) break :fix; + + // Build a new error list without the ambiguous operator precedence errors/notes + var fixed_errors: std.ArrayList(std.zig.Ast.Error) = try .initCapacity(gpa, tree.errors.len - (operators_to_fix.count() * 3)); + for (tree.errors) |err| { + switch (err.tag) { + .ambiguous_operator_precedence, + .illegal_chained_operators, + .previous_ambiguous_operator, + .hint_operator_precedence_left, + .hint_operator_precedence_right, + => continue, + else => { + fixed_errors.appendAssumeCapacity(err); + }, + } + } + gpa.free(tree.errors); + tree.errors = fixed_errors.toOwnedSliceAssert(); + + // Find the nodes we need to add parens to and add them to the list + for (tree.nodes.items(.tag), 0..) |tag, i| { + switch (tag) { + .merge_error_sets, + .mul, + .div, + .mod, + .mul_wrap, + .mul_sat, + .add, + .sub, + .array_cat, + .add_wrap, + .sub_wrap, + .add_sat, + .sub_sat, + .shl, + .shl_sat, + .shr, + .bit_and, + .bit_xor, + .bit_or, + .@"orelse", + .@"catch", + .bool_and, + .bool_or, + => { + const main_tok = tree.nodes.get(i).main_token; + if (operators_to_fix.contains(main_tok)) { + try fixups.insert_parentheses.put(gpa, @fromBackingInt(@intCast(i)), {}); + } + }, + else => {}, + } + } + } + if (tree.errors.len != 0) { try std.zig.printAstErrorsToStderr(gpa, io, tree, file_path, fmt.color); fmt.any_error = true; @@ -497,7 +576,7 @@ fn fmtPathFile( fmt.out_buffer.clearRetainingCapacity(); try fmt.out_buffer.ensureTotalCapacity(source_code.len); - tree.render(gpa, &fmt.out_buffer.writer, .{}) catch |err| switch (err) { + tree.render(gpa, &fmt.out_buffer.writer, fixups) catch |err| switch (err) { error.WriteFailed, error.OutOfMemory => return error.OutOfMemory, }; if (!mem.eql(u8, fmt.out_buffer.written(), source_code)) {