authorgravatar for danielchasehooper@gmail.comDaniel Hooper <danielchasehooper@gmail.com> 2022-03-20 06:55:04-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-03-20 12:55:04+02:00
log911c839e97194eb270389b03d4d364659c46a5ac
tree1b6579a3b1b472c26d8d3879477b7039512da8ea
parent0576086395774389a9f38d960f9ed5102a813bdb
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

add error when binary ops don't have matching whitespace on both sides

This change also moves the warning about "&&" from the AstGen into the parser so that the "&&" warning can supersede the whitespace warning.

6 files changed, 96 insertions(+), 42 deletions(-)

lib/std/zig/Ast.zig+8-1
......@@ -328,7 +328,12 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
328328 .expected_initializer => {
329329 return stream.writeAll("expected field initializer");
330330 },
331
331 .mismatched_binary_op_whitespace => {
332 return stream.print("binary operator `{s}` has whitespace on one side, but not the other.", .{token_tags[parse_error.token].lexeme().?});
333 },
334 .invalid_ampersand_ampersand => {
335 return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");
336 },
332337 .previous_field => {
333338 return stream.writeAll("field before declarations here");
334339 },
......@@ -2534,6 +2539,8 @@ pub const Error = struct {
25342539 expected_comma_after_initializer,
25352540 expected_comma_after_switch_prong,
25362541 expected_initializer,
2542 mismatched_binary_op_whitespace,
2543 invalid_ampersand_ampersand,
25372544
25382545 previous_field,
25392546 next_field,
lib/std/zig/parse.zig+17-6
......@@ -1451,13 +1451,11 @@ const Parser = struct {
14511451 if (info.prec == banned_prec) {
14521452 return p.fail(.chained_comparison_operators);
14531453 }
1454
14541455 const oper_token = p.nextToken();
1455 // Special-case handling for "catch" and "&&".
1456 switch (tok_tag) {
1457 .keyword_catch => {
1458 _ = try p.parsePayload();
1459 },
1460 else => {},
1456 // Special-case handling for "catch"
1457 if (tok_tag == .keyword_catch) {
1458 _ = try p.parsePayload();
14611459 }
14621460 const rhs = try p.parseExprPrecedence(info.prec + 1);
14631461 if (rhs == 0) {
......@@ -1465,6 +1463,19 @@ const Parser = struct {
14651463 return node;
14661464 }
14671465
1466 {
1467 const tok_len = tok_tag.lexeme().?.len;
1468 const char_before = p.source[p.token_starts[oper_token] - 1];
1469 const char_after = p.source[p.token_starts[oper_token] + tok_len];
1470 if (tok_tag == .ampersand and char_after == '&') {
1471 // without types we don't know if '&&' was intended as 'bitwise_and address_of', or a c-style logical_and
1472 // The best the parser can do is recommend changing it to 'and' or ' & &'
1473 try p.warnMsg(.{ .tag = .invalid_ampersand_ampersand, .token = oper_token });
1474 } else if (std.ascii.isSpace(char_before) != std.ascii.isSpace(char_after)) {
1475 try p.warnMsg(.{ .tag = .mismatched_binary_op_whitespace, .token = oper_token });
1476 }
1477 }
1478
14681479 node = try p.addNode(.{
14691480 .tag = info.tag,
14701481 .main_token = oper_token,
lib/std/zig/parser_test.zig+68-15
......@@ -4203,7 +4203,7 @@ test "zig fmt: integer literals with underscore separators" {
42034203 \\const
42044204 \\ x =
42054205 \\ 1_234_567
4206 \\ +(0b0_1-0o7_0+0xff_FF ) + 0_0;
4206 \\ + (0b0_1-0o7_0+0xff_FF ) + 0_0;
42074207 ,
42084208 \\const x =
42094209 \\ 1_234_567 + (0b0_1 - 0o7_0 + 0xff_FF) + 0_0;
......@@ -5105,7 +5105,7 @@ test "recovery: missing comma" {
51055105 \\ 2 => {}
51065106 \\ 3 => {}
51075107 \\ else => {
5108 \\ foo && bar +;
5108 \\ foo & bar +;
51095109 \\ }
51105110 \\ }
51115111 \\}
......@@ -5139,7 +5139,7 @@ test "recovery: extra qualifier" {
51395139test "recovery: missing return type" {
51405140 try testError(
51415141 \\fn foo() {
5142 \\ a && b;
5142 \\ a & b;
51435143 \\}
51445144 \\test ""
51455145 , &[_]Error{
......@@ -5154,7 +5154,7 @@ test "recovery: continue after invalid decl" {
51545154 \\ inline;
51555155 \\}
51565156 \\pub test "" {
5157 \\ async a && b;
5157 \\ async a & b;
51585158 \\}
51595159 , &[_]Error{
51605160 .expected_token,
......@@ -5163,7 +5163,7 @@ test "recovery: continue after invalid decl" {
51635163 });
51645164 try testError(
51655165 \\threadlocal test "" {
5166 \\ @a && b;
5166 \\ @a & b;
51675167 \\}
51685168 , &[_]Error{
51695169 .expected_var_decl,
......@@ -5173,12 +5173,12 @@ test "recovery: continue after invalid decl" {
51735173
51745174test "recovery: invalid extern/inline" {
51755175 try testError(
5176 \\inline test "" { a && b; }
5176 \\inline test "" { a & b; }
51775177 , &[_]Error{
51785178 .expected_fn,
51795179 });
51805180 try testError(
5181 \\extern "" test "" { a && b; }
5181 \\extern "" test "" { a & b; }
51825182 , &[_]Error{
51835183 .expected_var_decl_or_fn,
51845184 });
......@@ -5187,8 +5187,8 @@ test "recovery: invalid extern/inline" {
51875187test "recovery: missing semicolon" {
51885188 try testError(
51895189 \\test "" {
5190 \\ comptime a && b
5191 \\ c && d
5190 \\ comptime a & b
5191 \\ c & d
51925192 \\ @foo
51935193 \\}
51945194 , &[_]Error{
......@@ -5206,7 +5206,7 @@ test "recovery: invalid container members" {
52065206 \\bar@,
52075207 \\while (a == 2) { test "" {}}
52085208 \\test "" {
5209 \\ a && b
5209 \\ a & b
52105210 \\}
52115211 , &[_]Error{
52125212 .expected_expr,
......@@ -5224,7 +5224,7 @@ test "recovery: extra '}' at top level" {
52245224 try testError(
52255225 \\}}}
52265226 \\test "" {
5227 \\ a && b;
5227 \\ a & b;
52285228 \\}
52295229 , &[_]Error{
52305230 .expected_token,
......@@ -5244,7 +5244,7 @@ test "recovery: mismatched bracket at top level" {
52445244test "recovery: invalid global error set access" {
52455245 try testError(
52465246 \\test "" {
5247 \\ error && foo;
5247 \\ error & foo;
52485248 \\}
52495249 , &[_]Error{
52505250 .expected_token,
......@@ -5259,13 +5259,15 @@ test "recovery: invalid asterisk after pointer dereference" {
52595259 \\}
52605260 , &[_]Error{
52615261 .asterisk_after_ptr_deref,
5262 .mismatched_binary_op_whitespace,
52625263 });
52635264 try testError(
52645265 \\test "" {
5265 \\ var sequence = "repeat".** 10&&a;
5266 \\ var sequence = "repeat".** 10&a;
52665267 \\}
52675268 , &[_]Error{
52685269 .asterisk_after_ptr_deref,
5270 .mismatched_binary_op_whitespace,
52695271 });
52705272}
52715273
......@@ -5275,7 +5277,7 @@ test "recovery: missing semicolon after if, for, while stmt" {
52755277 \\ if (foo) bar
52765278 \\ for (foo) |a| bar
52775279 \\ while (foo) bar
5278 \\ a && b;
5280 \\ a & b;
52795281 \\}
52805282 , &[_]Error{
52815283 .expected_semi_or_else,
......@@ -5373,6 +5375,54 @@ test "recovery: eof in c pointer" {
53735375 });
53745376}
53755377
5378test "matching whitespace on minus op" {
5379 try testError(
5380 \\ _ = 2 -1,
5381 \\ _ = 2- 1,
5382 \\ _ = 2-
5383 \\ 2,
5384 \\ _ = 2
5385 \\ -2,
5386 , &[_]Error{
5387 .mismatched_binary_op_whitespace,
5388 .mismatched_binary_op_whitespace,
5389 .mismatched_binary_op_whitespace,
5390 .mismatched_binary_op_whitespace,
5391 });
5392
5393 try testError(
5394 \\ _ = - 1,
5395 \\ _ = -1,
5396 \\ _ = 2 - -1,
5397 \\ _ = 2 - 1,
5398 \\ _ = 2-1,
5399 \\ _ = 2 -
5400 \\1,
5401 \\ _ = 2
5402 \\ - 1,
5403 , &[_]Error{});
5404}
5405
5406test "ampersand" {
5407 try testError(
5408 \\ _ = bar && foo,
5409 \\ _ = bar&&foo,
5410 \\ _ = bar& & foo,
5411 \\ _ = bar& &foo,
5412 , &.{
5413 .invalid_ampersand_ampersand,
5414 .invalid_ampersand_ampersand,
5415 .mismatched_binary_op_whitespace,
5416 .mismatched_binary_op_whitespace,
5417 });
5418
5419 try testError(
5420 \\ _ = bar & &foo,
5421 \\ _ = bar & &&foo,
5422 \\ _ = &&foo,
5423 , &.{});
5424}
5425
53765426const std = @import("std");
53775427const mem = std.mem;
53785428const print = std.debug.print;
......@@ -5466,7 +5516,10 @@ fn testError(source: [:0]const u8, expected_errors: []const Error) !void {
54665516 var tree = try std.zig.parse(std.testing.allocator, source);
54675517 defer tree.deinit(std.testing.allocator);
54685518
5469 try std.testing.expectEqual(expected_errors.len, tree.errors.len);
5519 std.testing.expectEqual(expected_errors.len, tree.errors.len) catch |err| {
5520 std.debug.print("errors found: {any}\n", .{tree.errors});
5521 return err;
5522 };
54705523 for (expected_errors) |expected, i| {
54715524 try std.testing.expectEqual(expected, tree.errors[i].tag);
54725525 }
src/AstGen.zig+1-18
......@@ -669,24 +669,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
669669 .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
670670 .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat),
671671
672 .bit_and => {
673 const current_ampersand_token = main_tokens[node];
674 if (token_tags[current_ampersand_token + 1] == .ampersand) {
675 const token_starts = tree.tokens.items(.start);
676 const current_token_offset = token_starts[current_ampersand_token];
677 const next_token_offset = token_starts[current_ampersand_token + 1];
678 if (current_token_offset + 1 == next_token_offset) {
679 return astgen.failTok(
680 current_ampersand_token,
681 "`&&` is invalid; note that `and` is boolean AND",
682 .{},
683 );
684 }
685 }
686
687 return simpleBinOp(gz, scope, rl, node, .bit_and);
688 },
689
672 .bit_and => return simpleBinOp(gz, scope, rl, node, .bit_and),
690673 .bit_or => return simpleBinOp(gz, scope, rl, node, .bit_or),
691674 .bit_xor => return simpleBinOp(gz, scope, rl, node, .xor),
692675 .bang_equal => return simpleBinOp(gz, scope, rl, node, .cmp_neq),
test/compile_errors.zig+1-1
......@@ -3259,7 +3259,7 @@ pub fn addCases(ctx: *TestContext) !void {
32593259 \\ return 5678;
32603260 \\}
32613261 , &[_][]const u8{
3262 "tmp.zig:2:11: error: `&&` is invalid; note that `and` is boolean AND",
3262 "tmp.zig:2:11: error: ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND",
32633263 });
32643264
32653265 ctx.objErrStage1("attempted `||` on boolean values",
test/stage2/x86_64.zig+1-1
......@@ -1555,7 +1555,7 @@ pub fn addCases(ctx: *TestContext) !void {
15551555
15561556 case.addError(
15571557 \\pub const a = if (true && false) 1 else 2;
1558 , &[_][]const u8{":1:24: error: `&&` is invalid; note that `and` is boolean AND"});
1558 , &[_][]const u8{":1:24: error: ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND"});
15591559
15601560 case.addError(
15611561 \\pub fn main() void {