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
signature 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 {...@@ -328,7 +328,12 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
328 .expected_initializer => {328 .expected_initializer => {
329 return stream.writeAll("expected field initializer");329 return stream.writeAll("expected field initializer");
330 },330 },
331331 .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 },
332 .previous_field => {337 .previous_field => {
333 return stream.writeAll("field before declarations here");338 return stream.writeAll("field before declarations here");
334 },339 },
...@@ -2534,6 +2539,8 @@ pub const Error = struct {...@@ -2534,6 +2539,8 @@ pub const Error = struct {
2534 expected_comma_after_initializer,2539 expected_comma_after_initializer,
2535 expected_comma_after_switch_prong,2540 expected_comma_after_switch_prong,
2536 expected_initializer,2541 expected_initializer,
2542 mismatched_binary_op_whitespace,
2543 invalid_ampersand_ampersand,
25372544
2538 previous_field,2545 previous_field,
2539 next_field,2546 next_field,
lib/std/zig/parse.zig+17-6
...@@ -1451,13 +1451,11 @@ const Parser = struct {...@@ -1451,13 +1451,11 @@ const Parser = struct {
1451 if (info.prec == banned_prec) {1451 if (info.prec == banned_prec) {
1452 return p.fail(.chained_comparison_operators);1452 return p.fail(.chained_comparison_operators);
1453 }1453 }
1454
1454 const oper_token = p.nextToken();1455 const oper_token = p.nextToken();
1455 // Special-case handling for "catch" and "&&".1456 // Special-case handling for "catch"
1456 switch (tok_tag) {1457 if (tok_tag == .keyword_catch) {
1457 .keyword_catch => {1458 _ = try p.parsePayload();
1458 _ = try p.parsePayload();
1459 },
1460 else => {},
1461 }1459 }
1462 const rhs = try p.parseExprPrecedence(info.prec + 1);1460 const rhs = try p.parseExprPrecedence(info.prec + 1);
1463 if (rhs == 0) {1461 if (rhs == 0) {
...@@ -1465,6 +1463,19 @@ const Parser = struct {...@@ -1465,6 +1463,19 @@ const Parser = struct {
1465 return node;1463 return node;
1466 }1464 }
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
1468 node = try p.addNode(.{1479 node = try p.addNode(.{
1469 .tag = info.tag,1480 .tag = info.tag,
1470 .main_token = oper_token,1481 .main_token = oper_token,
lib/std/zig/parser_test.zig+68-15
...@@ -4203,7 +4203,7 @@ test "zig fmt: integer literals with underscore separators" {...@@ -4203,7 +4203,7 @@ test "zig fmt: integer literals with underscore separators" {
4203 \\const4203 \\const
4204 \\ x =4204 \\ x =
4205 \\ 1_234_5674205 \\ 1_234_567
4206 \\ +(0b0_1-0o7_0+0xff_FF ) + 0_0;4206 \\ + (0b0_1-0o7_0+0xff_FF ) + 0_0;
4207 ,4207 ,
4208 \\const x =4208 \\const x =
4209 \\ 1_234_567 + (0b0_1 - 0o7_0 + 0xff_FF) + 0_0;4209 \\ 1_234_567 + (0b0_1 - 0o7_0 + 0xff_FF) + 0_0;
...@@ -5105,7 +5105,7 @@ test "recovery: missing comma" {...@@ -5105,7 +5105,7 @@ test "recovery: missing comma" {
5105 \\ 2 => {}5105 \\ 2 => {}
5106 \\ 3 => {}5106 \\ 3 => {}
5107 \\ else => {5107 \\ else => {
5108 \\ foo && bar +;5108 \\ foo & bar +;
5109 \\ }5109 \\ }
5110 \\ }5110 \\ }
5111 \\}5111 \\}
...@@ -5139,7 +5139,7 @@ test "recovery: extra qualifier" {...@@ -5139,7 +5139,7 @@ test "recovery: extra qualifier" {
5139test "recovery: missing return type" {5139test "recovery: missing return type" {
5140 try testError(5140 try testError(
5141 \\fn foo() {5141 \\fn foo() {
5142 \\ a && b;5142 \\ a & b;
5143 \\}5143 \\}
5144 \\test ""5144 \\test ""
5145 , &[_]Error{5145 , &[_]Error{
...@@ -5154,7 +5154,7 @@ test "recovery: continue after invalid decl" {...@@ -5154,7 +5154,7 @@ test "recovery: continue after invalid decl" {
5154 \\ inline;5154 \\ inline;
5155 \\}5155 \\}
5156 \\pub test "" {5156 \\pub test "" {
5157 \\ async a && b;5157 \\ async a & b;
5158 \\}5158 \\}
5159 , &[_]Error{5159 , &[_]Error{
5160 .expected_token,5160 .expected_token,
...@@ -5163,7 +5163,7 @@ test "recovery: continue after invalid decl" {...@@ -5163,7 +5163,7 @@ test "recovery: continue after invalid decl" {
5163 });5163 });
5164 try testError(5164 try testError(
5165 \\threadlocal test "" {5165 \\threadlocal test "" {
5166 \\ @a && b;5166 \\ @a & b;
5167 \\}5167 \\}
5168 , &[_]Error{5168 , &[_]Error{
5169 .expected_var_decl,5169 .expected_var_decl,
...@@ -5173,12 +5173,12 @@ test "recovery: continue after invalid decl" {...@@ -5173,12 +5173,12 @@ test "recovery: continue after invalid decl" {
51735173
5174test "recovery: invalid extern/inline" {5174test "recovery: invalid extern/inline" {
5175 try testError(5175 try testError(
5176 \\inline test "" { a && b; }5176 \\inline test "" { a & b; }
5177 , &[_]Error{5177 , &[_]Error{
5178 .expected_fn,5178 .expected_fn,
5179 });5179 });
5180 try testError(5180 try testError(
5181 \\extern "" test "" { a && b; }5181 \\extern "" test "" { a & b; }
5182 , &[_]Error{5182 , &[_]Error{
5183 .expected_var_decl_or_fn,5183 .expected_var_decl_or_fn,
5184 });5184 });
...@@ -5187,8 +5187,8 @@ test "recovery: invalid extern/inline" {...@@ -5187,8 +5187,8 @@ test "recovery: invalid extern/inline" {
5187test "recovery: missing semicolon" {5187test "recovery: missing semicolon" {
5188 try testError(5188 try testError(
5189 \\test "" {5189 \\test "" {
5190 \\ comptime a && b5190 \\ comptime a & b
5191 \\ c && d5191 \\ c & d
5192 \\ @foo5192 \\ @foo
5193 \\}5193 \\}
5194 , &[_]Error{5194 , &[_]Error{
...@@ -5206,7 +5206,7 @@ test "recovery: invalid container members" {...@@ -5206,7 +5206,7 @@ test "recovery: invalid container members" {
5206 \\bar@,5206 \\bar@,
5207 \\while (a == 2) { test "" {}}5207 \\while (a == 2) { test "" {}}
5208 \\test "" {5208 \\test "" {
5209 \\ a && b5209 \\ a & b
5210 \\}5210 \\}
5211 , &[_]Error{5211 , &[_]Error{
5212 .expected_expr,5212 .expected_expr,
...@@ -5224,7 +5224,7 @@ test "recovery: extra '}' at top level" {...@@ -5224,7 +5224,7 @@ test "recovery: extra '}' at top level" {
5224 try testError(5224 try testError(
5225 \\}}}5225 \\}}}
5226 \\test "" {5226 \\test "" {
5227 \\ a && b;5227 \\ a & b;
5228 \\}5228 \\}
5229 , &[_]Error{5229 , &[_]Error{
5230 .expected_token,5230 .expected_token,
...@@ -5244,7 +5244,7 @@ test "recovery: mismatched bracket at top level" {...@@ -5244,7 +5244,7 @@ test "recovery: mismatched bracket at top level" {
5244test "recovery: invalid global error set access" {5244test "recovery: invalid global error set access" {
5245 try testError(5245 try testError(
5246 \\test "" {5246 \\test "" {
5247 \\ error && foo;5247 \\ error & foo;
5248 \\}5248 \\}
5249 , &[_]Error{5249 , &[_]Error{
5250 .expected_token,5250 .expected_token,
...@@ -5259,13 +5259,15 @@ test "recovery: invalid asterisk after pointer dereference" {...@@ -5259,13 +5259,15 @@ test "recovery: invalid asterisk after pointer dereference" {
5259 \\}5259 \\}
5260 , &[_]Error{5260 , &[_]Error{
5261 .asterisk_after_ptr_deref,5261 .asterisk_after_ptr_deref,
5262 .mismatched_binary_op_whitespace,
5262 });5263 });
5263 try testError(5264 try testError(
5264 \\test "" {5265 \\test "" {
5265 \\ var sequence = "repeat".** 10&&a;5266 \\ var sequence = "repeat".** 10&a;
5266 \\}5267 \\}
5267 , &[_]Error{5268 , &[_]Error{
5268 .asterisk_after_ptr_deref,5269 .asterisk_after_ptr_deref,
5270 .mismatched_binary_op_whitespace,
5269 });5271 });
5270}5272}
52715273
...@@ -5275,7 +5277,7 @@ test "recovery: missing semicolon after if, for, while stmt" {...@@ -5275,7 +5277,7 @@ test "recovery: missing semicolon after if, for, while stmt" {
5275 \\ if (foo) bar5277 \\ if (foo) bar
5276 \\ for (foo) |a| bar5278 \\ for (foo) |a| bar
5277 \\ while (foo) bar5279 \\ while (foo) bar
5278 \\ a && b;5280 \\ a & b;
5279 \\}5281 \\}
5280 , &[_]Error{5282 , &[_]Error{
5281 .expected_semi_or_else,5283 .expected_semi_or_else,
...@@ -5373,6 +5375,54 @@ test "recovery: eof in c pointer" {...@@ -5373,6 +5375,54 @@ test "recovery: eof in c pointer" {
5373 });5375 });
5374}5376}
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
5376const std = @import("std");5426const std = @import("std");
5377const mem = std.mem;5427const mem = std.mem;
5378const print = std.debug.print;5428const print = std.debug.print;
...@@ -5466,7 +5516,10 @@ fn testError(source: [:0]const u8, expected_errors: []const Error) !void {...@@ -5466,7 +5516,10 @@ fn testError(source: [:0]const u8, expected_errors: []const Error) !void {
5466 var tree = try std.zig.parse(std.testing.allocator, source);5516 var tree = try std.zig.parse(std.testing.allocator, source);
5467 defer tree.deinit(std.testing.allocator);5517 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 };
5470 for (expected_errors) |expected, i| {5523 for (expected_errors) |expected, i| {
5471 try std.testing.expectEqual(expected, tree.errors[i].tag);5524 try std.testing.expectEqual(expected, tree.errors[i].tag);
5472 }5525 }
src/AstGen.zig+1-18
...@@ -669,24 +669,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr...@@ -669,24 +669,7 @@ fn expr(gz: *GenZir, scope: *Scope, rl: ResultLoc, node: Ast.Node.Index) InnerEr
669 .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),669 .mod => return simpleBinOp(gz, scope, rl, node, .mod_rem),
670 .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat),670 .shl_sat => return simpleBinOp(gz, scope, rl, node, .shl_sat),
671671
672 .bit_and => {672 .bit_and => return simpleBinOp(gz, scope, rl, node, .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
690 .bit_or => return simpleBinOp(gz, scope, rl, node, .bit_or),673 .bit_or => return simpleBinOp(gz, scope, rl, node, .bit_or),
691 .bit_xor => return simpleBinOp(gz, scope, rl, node, .xor),674 .bit_xor => return simpleBinOp(gz, scope, rl, node, .xor),
692 .bang_equal => return simpleBinOp(gz, scope, rl, node, .cmp_neq),675 .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 {...@@ -3259,7 +3259,7 @@ pub fn addCases(ctx: *TestContext) !void {
3259 \\ return 5678;3259 \\ return 5678;
3260 \\}3260 \\}
3261 , &[_][]const u8{3261 , &[_][]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",
3263 });3263 });
32643264
3265 ctx.objErrStage1("attempted `||` on boolean values",3265 ctx.objErrStage1("attempted `||` on boolean values",
test/stage2/x86_64.zig+1-1
...@@ -1555,7 +1555,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -1555,7 +1555,7 @@ pub fn addCases(ctx: *TestContext) !void {
15551555
1556 case.addError(1556 case.addError(
1557 \\pub const a = if (true && false) 1 else 2;1557 \\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
1560 case.addError(1560 case.addError(
1561 \\pub fn main() void {1561 \\pub fn main() void {