diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 39a1d68847..b485f915f0 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -328,7 +328,12 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { .expected_initializer => { return stream.writeAll("expected field initializer"); }, - + .mismatched_binary_op_whitespace => { + return stream.print("binary operator `{s}` has whitespace on one side, but not the other.", .{token_tags[parse_error.token].lexeme().?}); + }, + .invalid_ampersand_ampersand => { + return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND"); + }, .previous_field => { return stream.writeAll("field before declarations here"); }, @@ -2534,6 +2539,8 @@ pub const Error = struct { expected_comma_after_initializer, expected_comma_after_switch_prong, expected_initializer, + mismatched_binary_op_whitespace, + invalid_ampersand_ampersand, previous_field, next_field, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index d15e58eb1c..984b137bff 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1451,13 +1451,11 @@ const Parser = struct { if (info.prec == banned_prec) { return p.fail(.chained_comparison_operators); } + const oper_token = p.nextToken(); - // Special-case handling for "catch" and "&&". - switch (tok_tag) { - .keyword_catch => { - _ = try p.parsePayload(); - }, - else => {}, + // Special-case handling for "catch" + if (tok_tag == .keyword_catch) { + _ = try p.parsePayload(); } const rhs = try p.parseExprPrecedence(info.prec + 1); if (rhs == 0) { @@ -1465,6 +1463,19 @@ const Parser = struct { return node; } + { + const tok_len = tok_tag.lexeme().?.len; + const char_before = p.source[p.token_starts[oper_token] - 1]; + const char_after = p.source[p.token_starts[oper_token] + tok_len]; + if (tok_tag == .ampersand and char_after == '&') { + // without types we don't know if '&&' was intended as 'bitwise_and address_of', or a c-style logical_and + // The best the parser can do is recommend changing it to 'and' or ' & &' + try p.warnMsg(.{ .tag = .invalid_ampersand_ampersand, .token = oper_token }); + } else if (std.ascii.isSpace(char_before) != std.ascii.isSpace(char_after)) { + try p.warnMsg(.{ .tag = .mismatched_binary_op_whitespace, .token = oper_token }); + } + } + node = try p.addNode(.{ .tag = info.tag, .main_token = oper_token, diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index c9c598b1d6..af40d8352c 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -4203,7 +4203,7 @@ test "zig fmt: integer literals with underscore separators" { \\const \\ x = \\ 1_234_567 - \\ +(0b0_1-0o7_0+0xff_FF ) + 0_0; + \\ + (0b0_1-0o7_0+0xff_FF ) + 0_0; , \\const x = \\ 1_234_567 + (0b0_1 - 0o7_0 + 0xff_FF) + 0_0; @@ -5105,7 +5105,7 @@ test "recovery: missing comma" { \\ 2 => {} \\ 3 => {} \\ else => { - \\ foo && bar +; + \\ foo & bar +; \\ } \\ } \\} @@ -5139,7 +5139,7 @@ test "recovery: extra qualifier" { test "recovery: missing return type" { try testError( \\fn foo() { - \\ a && b; + \\ a & b; \\} \\test "" , &[_]Error{ @@ -5154,7 +5154,7 @@ test "recovery: continue after invalid decl" { \\ inline; \\} \\pub test "" { - \\ async a && b; + \\ async a & b; \\} , &[_]Error{ .expected_token, @@ -5163,7 +5163,7 @@ test "recovery: continue after invalid decl" { }); try testError( \\threadlocal test "" { - \\ @a && b; + \\ @a & b; \\} , &[_]Error{ .expected_var_decl, @@ -5173,12 +5173,12 @@ test "recovery: continue after invalid decl" { test "recovery: invalid extern/inline" { try testError( - \\inline test "" { a && b; } + \\inline test "" { a & b; } , &[_]Error{ .expected_fn, }); try testError( - \\extern "" test "" { a && b; } + \\extern "" test "" { a & b; } , &[_]Error{ .expected_var_decl_or_fn, }); @@ -5187,8 +5187,8 @@ test "recovery: invalid extern/inline" { test "recovery: missing semicolon" { try testError( \\test "" { - \\ comptime a && b - \\ c && d + \\ comptime a & b + \\ c & d \\ @foo \\} , &[_]Error{ @@ -5206,7 +5206,7 @@ test "recovery: invalid container members" { \\bar@, \\while (a == 2) { test "" {}} \\test "" { - \\ a && b + \\ a & b \\} , &[_]Error{ .expected_expr, @@ -5224,7 +5224,7 @@ test "recovery: extra '}' at top level" { try testError( \\}}} \\test "" { - \\ a && b; + \\ a & b; \\} , &[_]Error{ .expected_token, @@ -5244,7 +5244,7 @@ test "recovery: mismatched bracket at top level" { test "recovery: invalid global error set access" { try testError( \\test "" { - \\ error && foo; + \\ error & foo; \\} , &[_]Error{ .expected_token, @@ -5259,13 +5259,15 @@ test "recovery: invalid asterisk after pointer dereference" { \\} , &[_]Error{ .asterisk_after_ptr_deref, + .mismatched_binary_op_whitespace, }); try testError( \\test "" { - \\ var sequence = "repeat".** 10&&a; + \\ var sequence = "repeat".** 10&a; \\} , &[_]Error{ .asterisk_after_ptr_deref, + .mismatched_binary_op_whitespace, }); } @@ -5275,7 +5277,7 @@ test "recovery: missing semicolon after if, for, while stmt" { \\ if (foo) bar \\ for (foo) |a| bar \\ while (foo) bar - \\ a && b; + \\ a & b; \\} , &[_]Error{ .expected_semi_or_else, @@ -5373,6 +5375,54 @@ test "recovery: eof in c pointer" { }); } +test "matching whitespace on minus op" { + try testError( + \\ _ = 2 -1, + \\ _ = 2- 1, + \\ _ = 2- + \\ 2, + \\ _ = 2 + \\ -2, + , &[_]Error{ + .mismatched_binary_op_whitespace, + .mismatched_binary_op_whitespace, + .mismatched_binary_op_whitespace, + .mismatched_binary_op_whitespace, + }); + + try testError( + \\ _ = - 1, + \\ _ = -1, + \\ _ = 2 - -1, + \\ _ = 2 - 1, + \\ _ = 2-1, + \\ _ = 2 - + \\1, + \\ _ = 2 + \\ - 1, + , &[_]Error{}); +} + +test "ampersand" { + try testError( + \\ _ = bar && foo, + \\ _ = bar&&foo, + \\ _ = bar& & foo, + \\ _ = bar& &foo, + , &.{ + .invalid_ampersand_ampersand, + .invalid_ampersand_ampersand, + .mismatched_binary_op_whitespace, + .mismatched_binary_op_whitespace, + }); + + try testError( + \\ _ = bar & &foo, + \\ _ = bar & &&foo, + \\ _ = &&foo, + , &.{}); +} + const std = @import("std"); const mem = std.mem; const print = std.debug.print; @@ -5466,7 +5516,10 @@ fn testError(source: [:0]const u8, expected_errors: []const Error) !void { var tree = try std.zig.parse(std.testing.allocator, source); defer tree.deinit(std.testing.allocator); - try std.testing.expectEqual(expected_errors.len, tree.errors.len); + std.testing.expectEqual(expected_errors.len, tree.errors.len) catch |err| { + std.debug.print("errors found: {any}\n", .{tree.errors}); + return err; + }; for (expected_errors) |expected, i| { try std.testing.expectEqual(expected, tree.errors[i].tag); } diff --git a/src/AstGen.zig b/src/AstGen.zig index 1fe524572b..4221f0cd14 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -669,24 +669,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem), .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat), - .bit_and => { - const current_ampersand_token = main_tokens[node]; - if (token_tags[current_ampersand_token + 1] == .ampersand) { - const token_starts = tree.tokens.items(.start); - const current_token_offset = token_starts[current_ampersand_token]; - const next_token_offset = token_starts[current_ampersand_token + 1]; - if (current_token_offset + 1 == next_token_offset) { - return astgen.failTok( - current_ampersand_token, - "`&&` is invalid; note that `and` is boolean AND", - .{}, - ); - } - } - - return simpleBinOp(gz, scope, rl, node, .bit_and); - }, - + .bit_and => return simpleBinOp(gz, scope, rl, node, .bit_and), .bit_or => return simpleBinOp(gz, scope, rl, node, .bit_or), .bit_xor => return simpleBinOp(gz, scope, rl, node, .xor), .bang_equal => return simpleBinOp(gz, scope, rl, node, .cmp_neq), diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 3a84617222..81083cb049 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -3259,7 +3259,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ return 5678; \\} , &[_][]const u8{ - "tmp.zig:2:11: error: `&&` is invalid; note that `and` is boolean AND", + "tmp.zig:2:11: error: ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND", }); ctx.objErrStage1("attempted `||` on boolean values", diff --git a/test/stage2/x86_64.zig b/test/stage2/x86_64.zig index 518c505332..38dafd8fc0 100644 --- a/test/stage2/x86_64.zig +++ b/test/stage2/x86_64.zig @@ -1555,7 +1555,7 @@ pub fn addCases(ctx: *TestContext) !void { case.addError( \\pub const a = if (true && false) 1 else 2; - , &[_][]const u8{":1:24: error: `&&` is invalid; note that `and` is boolean AND"}); + , &[_][]const u8{":1:24: error: ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND"}); case.addError( \\pub fn main() void {