From 9c36cf92f009eda14cb773e89148547dadb137c6 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 16 Feb 2022 13:06:11 +0200 Subject: [PATCH 1/5] parser: make some errors point to end of previous token For some errors if the found token is not on the same line as the previous token, point to the end of the previous token. This usually results in more helpful errors. --- lib/std/zig/Ast.zig | 70 +++++++--------- lib/std/zig/parse.zig | 162 +++++++++++++++--------------------- lib/std/zig/parser_test.zig | 6 +- lib/std/zig/tokenizer.zig | 13 ++- src/Module.zig | 8 +- src/main.zig | 8 +- test/compile_errors.zig | 48 +++++------ test/stage2/cbe.zig | 2 +- 8 files changed, 148 insertions(+), 169 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index cecdc3adeb..64549d3cf6 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -66,20 +66,11 @@ pub fn renderToArrayList(tree: Ast, buffer: *std.ArrayList(u8)) RenderError!void /// Returns an extra offset for column and byte offset of errors that /// should point after the token in the error message. -pub fn errorOffset(tree: Ast, error_tag: Error.Tag, token: TokenIndex) u32 { - return switch (error_tag) { - .expected_semi_after_decl, - .expected_semi_after_stmt, - .expected_comma_after_field, - .expected_comma_after_arg, - .expected_comma_after_param, - .expected_comma_after_initializer, - .expected_comma_after_switch_prong, - .expected_semi_or_else, - .expected_semi_or_lbrace, - => @intCast(u32, tree.tokenSlice(token).len), - else => 0, - }; +pub fn errorOffset(tree: Ast, parse_error: Error) u32 { + return if (parse_error.token_is_prev) + @intCast(u32, tree.tokenSlice(parse_error.token).len) + else + 0; } pub fn tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location { @@ -162,22 +153,22 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { }, .expected_block => { return stream.print("expected block or field, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_block_or_assignment => { return stream.print("expected block or assignment, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_block_or_expr => { return stream.print("expected block or expression, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_block_or_field => { return stream.print("expected block or field, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_container_members => { @@ -187,42 +178,42 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { }, .expected_expr => { return stream.print("expected expression, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_expr_or_assignment => { return stream.print("expected expression or assignment, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_fn => { return stream.print("expected function, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_inlinable => { return stream.print("expected 'while' or 'for', found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_labelable => { return stream.print("expected 'while', 'for', 'inline', 'suspend', or '{{', found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_param_list => { return stream.print("expected parameter list, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_prefix_expr => { return stream.print("expected prefix expression, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_primary_type_expr => { return stream.print("expected primary type expression, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_pub_item => { @@ -230,7 +221,7 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { }, .expected_return_type => { return stream.print("expected return type expression, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_semi_or_else => { @@ -244,39 +235,34 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { token_tags[parse_error.token].symbol(), }); }, - .expected_string_literal => { - return stream.print("expected string literal, found '{s}'", .{ - token_tags[parse_error.token].symbol(), - }); - }, .expected_suffix_op => { return stream.print("expected pointer dereference, optional unwrap, or field access, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_type_expr => { return stream.print("expected type expression, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_var_decl => { return stream.print("expected variable declaration, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_var_decl_or_fn => { return stream.print("expected variable declaration or function, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_loop_payload => { return stream.print("expected loop payload, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .expected_container => { return stream.print("expected a struct, enum or union, found '{s}'", .{ - token_tags[parse_error.token].symbol(), + token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)].symbol(), }); }, .extern_fn_body => { @@ -341,9 +327,12 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { .expected_comma_after_switch_prong => { return stream.writeAll("expected ',' after switch prong"); }, + .expected_initializer => { + return stream.writeAll("expected field initializer"); + }, .expected_token => { - const found_tag = token_tags[parse_error.token]; + const found_tag = token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)]; const expected_symbol = parse_error.extra.expected_tag.symbol(); switch (found_tag) { .invalid => return stream.print("expected '{s}', found invalid bytes", .{ @@ -2483,6 +2472,7 @@ pub const full = struct { pub const Error = struct { tag: Tag, + token_is_prev: bool = false, token: TokenIndex, extra: union { none: void, @@ -2511,7 +2501,6 @@ pub const Error = struct { expected_semi_or_else, expected_semi_or_lbrace, expected_statement, - expected_string_literal, expected_suffix_op, expected_type_expr, expected_var_decl, @@ -2539,6 +2528,7 @@ pub const Error = struct { expected_comma_after_param, expected_comma_after_initializer, expected_comma_after_switch_prong, + expected_initializer, /// `expected_tag` is populated. expected_token, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 9050ba1bc4..bc3c2a35b3 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -147,11 +147,6 @@ const Parser = struct { return result; } - fn warn(p: *Parser, tag: Ast.Error.Tag) error{OutOfMemory}!void { - @setCold(true); - try p.warnMsg(.{ .tag = tag, .token = p.tok_i }); - } - fn warnExpected(p: *Parser, expected_token: Token.Tag) error{OutOfMemory}!void { @setCold(true); try p.warnMsg(.{ @@ -161,13 +156,53 @@ const Parser = struct { }); } - fn warnExpectedAfter(p: *Parser, error_tag: AstError.Tag) error{OutOfMemory}!void { + fn warn(p: *Parser, error_tag: AstError.Tag) error{OutOfMemory}!void { @setCold(true); - try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i - 1 }); + try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i }); } fn warnMsg(p: *Parser, msg: Ast.Error) error{OutOfMemory}!void { @setCold(true); + switch (msg.tag) { + .expected_semi_after_decl, + .expected_semi_after_stmt, + .expected_comma_after_field, + .expected_comma_after_arg, + .expected_comma_after_param, + .expected_comma_after_initializer, + .expected_comma_after_switch_prong, + .expected_semi_or_else, + .expected_semi_or_lbrace, + .expected_token, + .expected_block, + .expected_block_or_assignment, + .expected_block_or_expr, + .expected_block_or_field, + .expected_container_members, + .expected_expr, + .expected_expr_or_assignment, + .expected_fn, + .expected_inlinable, + .expected_labelable, + .expected_param_list, + .expected_prefix_expr, + .expected_primary_type_expr, + .expected_pub_item, + .expected_return_type, + .expected_suffix_op, + .expected_type_expr, + .expected_var_decl, + .expected_var_decl_or_fn, + .expected_loop_payload, + .expected_container, + => if (msg.token != 0 and !p.tokensOnSameLine(msg.token - 1, msg.token)) { + var copy = msg; + copy.token_is_prev = true; + copy.token -= 1; + return p.errors.append(p.gpa, copy); + }, + else => {}, + } try p.errors.append(p.gpa, msg); } @@ -264,7 +299,7 @@ const Parser = struct { } // There is not allowed to be a decl after a field with no comma. // Report error but recover parser. - try p.warnExpectedAfter(.expected_comma_after_field); + try p.warn(.expected_comma_after_field); p.findNextContainerMember(); } }, @@ -367,7 +402,7 @@ const Parser = struct { } // There is not allowed to be a decl after a field with no comma. // Report error but recover parser. - try p.warnExpectedAfter(.expected_comma_after_field); + try p.warn(.expected_comma_after_field); p.findNextContainerMember(); } }, @@ -585,7 +620,7 @@ const Parser = struct { // Since parseBlock only return error.ParseError on // a missing '}' we can assume this function was // supposed to end here. - try p.warnExpectedAfter(.expected_semi_or_lbrace); + try p.warn(.expected_semi_or_lbrace); return null_node; }, } @@ -996,7 +1031,7 @@ const Parser = struct { }; _ = p.eatToken(.keyword_else) orelse { if (else_required) { - try p.warnExpectedAfter(.expected_semi_or_else); + try p.warn(.expected_semi_or_else); } return p.addNode(.{ .tag = .if_simple, @@ -1091,7 +1126,7 @@ const Parser = struct { }; _ = p.eatToken(.keyword_else) orelse { if (else_required) { - try p.warnExpectedAfter(.expected_semi_or_else); + try p.warn(.expected_semi_or_else); } return p.addNode(.{ .tag = .for_simple, @@ -1166,7 +1201,7 @@ const Parser = struct { }; _ = p.eatToken(.keyword_else) orelse { if (else_required) { - try p.warnExpectedAfter(.expected_semi_or_else); + try p.warn(.expected_semi_or_else); } if (cont_expr == 0) { return p.addNode(.{ @@ -2050,7 +2085,7 @@ const Parser = struct { }, .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_initializer), + else => try p.warn(.expected_comma_after_initializer), } if (p.eatToken(.r_brace)) |_| break; const next = try p.expectFieldInit(); @@ -2091,7 +2126,7 @@ const Parser = struct { }, .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_initializer), + else => try p.warn(.expected_comma_after_initializer), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2170,7 +2205,7 @@ const Parser = struct { }, .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_arg), + else => try p.warn(.expected_comma_after_arg), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2226,7 +2261,7 @@ const Parser = struct { }, .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_arg), + else => try p.warn(.expected_comma_after_arg), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2467,7 +2502,7 @@ const Parser = struct { }, .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_initializer), + else => try p.warn(.expected_comma_after_initializer), } if (p.eatToken(.r_brace)) |_| break; const next = try p.expectFieldInit(); @@ -2519,7 +2554,7 @@ const Parser = struct { }, .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_initializer), + else => try p.warn(.expected_comma_after_initializer), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2580,7 +2615,7 @@ const Parser = struct { }, .colon, .r_paren, .r_bracket => return p.failExpected(.r_brace), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_field), + else => try p.warn(.expected_comma_after_field), } } return p.addNode(.{ @@ -2879,7 +2914,7 @@ const Parser = struct { p.tok_i += 2; return identifier; } - return 0; + return null_node; } /// FieldInit <- DOT IDENTIFIER EQUAL Expr @@ -2896,9 +2931,12 @@ const Parser = struct { } fn expectFieldInit(p: *Parser) !Node.Index { - _ = try p.expectToken(.period); - _ = try p.expectToken(.identifier); - _ = try p.expectToken(.equal); + if (p.token_tags[p.tok_i] != .period or + p.token_tags[p.tok_i + 1] != .identifier or + p.token_tags[p.tok_i + 2] != .equal) + return p.fail(.expected_initializer); + + p.tok_i += 3; return p.expectExpr(); } @@ -3413,7 +3451,7 @@ const Parser = struct { // All possible delimiters. .colon, .r_paren, .r_brace, .r_bracket => break, // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_switch_prong), + else => try p.warn(.expected_comma_after_switch_prong), } } return p.listToSpan(p.scratch.items[scratch_top..]); @@ -3442,7 +3480,7 @@ const Parser = struct { }, .colon, .r_brace, .r_bracket => return p.failExpected(.r_paren), // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_param), + else => try p.warn(.expected_comma_after_param), } } if (varargs == .nonfinal) { @@ -3486,7 +3524,7 @@ const Parser = struct { break; }, // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpectedAfter(.expected_comma_after_arg), + else => try p.warn(.expected_comma_after_arg), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -3530,57 +3568,6 @@ const Parser = struct { } } - // string literal or multiline string literal - fn parseStringLiteral(p: *Parser) !Node.Index { - switch (p.token_tags[p.tok_i]) { - .string_literal => { - const main_token = p.nextToken(); - return p.addNode(.{ - .tag = .string_literal, - .main_token = main_token, - .data = .{ - .lhs = undefined, - .rhs = undefined, - }, - }); - }, - .multiline_string_literal_line => { - const first_line = p.nextToken(); - while (p.token_tags[p.tok_i] == .multiline_string_literal_line) { - p.tok_i += 1; - } - return p.addNode(.{ - .tag = .multiline_string_literal, - .main_token = first_line, - .data = .{ - .lhs = first_line, - .rhs = p.tok_i - 1, - }, - }); - }, - else => return null_node, - } - } - - fn expectStringLiteral(p: *Parser) !Node.Index { - const node = try p.parseStringLiteral(); - if (node == 0) { - return p.fail(.expected_string_literal); - } - return node; - } - - fn expectIntegerLiteral(p: *Parser) !Node.Index { - return p.addNode(.{ - .tag = .integer_literal, - .main_token = try p.expectToken(.integer_literal), - .data = .{ - .lhs = undefined, - .rhs = undefined, - }, - }); - } - /// KEYWORD_if LPAREN Expr RPAREN PtrPayload? Body (KEYWORD_else Payload? Body)? fn parseIf(p: *Parser, bodyParseFn: fn (p: *Parser) Error!Node.Index) !Node.Index { const if_token = p.eatToken(.keyword_if) orelse return null_node; @@ -3649,25 +3636,14 @@ const Parser = struct { } fn expectToken(p: *Parser, tag: Token.Tag) Error!TokenIndex { - const token = p.nextToken(); - if (p.token_tags[token] != tag) { - p.tok_i -= 1; // Go back so that we can recover properly. + if (p.token_tags[p.tok_i] != tag) { return p.failMsg(.{ .tag = .expected_token, - .token = token, + .token = p.tok_i, .extra = .{ .expected_tag = tag }, }); } - return token; - } - - fn expectTokenRecoverable(p: *Parser, tag: Token.Tag) !?TokenIndex { - if (p.token_tags[p.tok_i] != tag) { - try p.warnExpected(tag); - return null; - } else { - return p.nextToken(); - } + return p.nextToken(); } fn expectSemicolon(p: *Parser, error_tag: AstError.Tag, recoverable: bool) Error!void { @@ -3675,7 +3651,7 @@ const Parser = struct { _ = p.nextToken(); return; } - try p.warnExpectedAfter(error_tag); + try p.warn(error_tag); if (!recoverable) return error.ParseError; } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 5eca272b62..e5932ddfc3 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -5057,7 +5057,9 @@ test "recovery: block statements" { \\ inline; \\} , &[_]Error{ - .invalid_token, + .expected_expr, + .expected_semi_after_stmt, + .expected_statement, .expected_inlinable, }); } @@ -5076,7 +5078,7 @@ test "recovery: missing comma" { , &[_]Error{ .expected_comma_after_switch_prong, .expected_comma_after_switch_prong, - .invalid_token, + .expected_expr, }); } diff --git a/lib/std/zig/tokenizer.zig b/lib/std/zig/tokenizer.zig index b6e1ced061..76b14df877 100644 --- a/lib/std/zig/tokenizer.zig +++ b/lib/std/zig/tokenizer.zig @@ -322,7 +322,18 @@ pub const Token = struct { } pub fn symbol(tag: Tag) []const u8 { - return tag.lexeme() orelse @tagName(tag); + return tag.lexeme() orelse switch (tag) { + .invalid => "invalid bytes", + .identifier => "an identifier", + .string_literal, .multiline_string_literal_line => "a string literal", + .char_literal => "a character literal", + .eof => "EOF", + .builtin => "a builtin function", + .integer_literal => "an integer literal", + .float_literal => "a floating point literal", + .doc_comment, .container_doc_comment => "a document comment", + else => unreachable, + }; } }; }; diff --git a/src/Module.zig b/src/Module.zig index 4ffd6925b6..280c58ade8 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2995,7 +2995,7 @@ pub fn astGenFile(mod: *Module, file: *File) !void { const token_starts = file.tree.tokens.items(.start); const token_tags = file.tree.tokens.items(.tag); - const extra_offset = file.tree.errorOffset(parse_err.tag, parse_err.token); + const extra_offset = file.tree.errorOffset(parse_err); try file.tree.renderError(parse_err, msg.writer()); const err_msg = try gpa.create(ErrorMsg); err_msg.* = .{ @@ -3006,9 +3006,9 @@ pub fn astGenFile(mod: *Module, file: *File) !void { }, .msg = msg.toOwnedSlice(), }; - if (token_tags[parse_err.token] == .invalid) { - const bad_off = @intCast(u32, file.tree.tokenSlice(parse_err.token).len); - const byte_abs = token_starts[parse_err.token] + bad_off; + if (token_tags[parse_err.token + @boolToInt(parse_err.token_is_prev)] == .invalid) { + const bad_off = @intCast(u32, file.tree.tokenSlice(parse_err.token + @boolToInt(parse_err.token_is_prev)).len); + const byte_abs = token_starts[parse_err.token + @boolToInt(parse_err.token_is_prev)] + bad_off; try mod.errNoteNonLazy(.{ .file_scope = file, .parent_decl_node = 0, diff --git a/src/main.zig b/src/main.zig index 05afc2b166..3d33268718 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4063,9 +4063,9 @@ fn printErrMsgToStdErr( var notes_buffer: [1]Compilation.AllErrors.Message = undefined; var notes_len: usize = 0; - if (token_tags[parse_error.token] == .invalid) { - const bad_off = @intCast(u32, tree.tokenSlice(parse_error.token).len); - const byte_offset = @intCast(u32, start_loc.line_start) + bad_off; + if (token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)] == .invalid) { + const bad_off = @intCast(u32, tree.tokenSlice(parse_error.token + @boolToInt(parse_error.token_is_prev)).len); + const byte_offset = @intCast(u32, start_loc.line_start) + @intCast(u32, start_loc.column) + bad_off; notes_buffer[notes_len] = .{ .src = .{ .src_path = path, @@ -4081,7 +4081,7 @@ fn printErrMsgToStdErr( notes_len += 1; } - const extra_offset = tree.errorOffset(parse_error.tag, parse_error.token); + const extra_offset = tree.errorOffset(parse_error); const message: Compilation.AllErrors.Message = .{ .src = .{ .src_path = path, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 79c17b4336..faed747a61 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1540,7 +1540,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ std.debug.assert(bad_float < 1.0); \\} , &[_][]const u8{ - "tmp.zig:5:29: error: invalid token: '.'", + "tmp.zig:5:29: error: expected expression, found '.'", }); ctx.objErrStage1("invalid exponent in float literal - 1", @@ -1549,7 +1549,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:28: note: invalid byte: 'a'", }); @@ -1559,7 +1559,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:29: note: invalid byte: 'F'", }); @@ -1569,7 +1569,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:23: note: invalid byte: '_'", }); @@ -1579,7 +1579,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:23: note: invalid byte: '.'", }); @@ -1589,7 +1589,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:25: note: invalid byte: ';'", }); @@ -1599,7 +1599,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:25: note: invalid byte: '_'", }); @@ -1609,7 +1609,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:26: note: invalid byte: '_'", }); @@ -1619,7 +1619,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:26: note: invalid byte: '_'", }); @@ -1629,7 +1629,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:28: note: invalid byte: ';'", }); @@ -1639,7 +1639,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:23: note: invalid byte: '_'", }); @@ -1649,7 +1649,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:25: note: invalid byte: '_'", }); @@ -1659,7 +1659,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:28: note: invalid byte: '_'", }); @@ -1669,7 +1669,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:23: note: invalid byte: 'x'", }); @@ -1679,7 +1679,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:23: note: invalid byte: '_'", }); @@ -1689,7 +1689,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:27: note: invalid byte: 'p'", }); @@ -1699,7 +1699,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:26: note: invalid byte: ';'", }); @@ -1709,7 +1709,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:28: note: invalid byte: ';'", }); @@ -1719,7 +1719,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:28: note: invalid byte: ';'", }); @@ -1729,7 +1729,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = bad; \\} , &[_][]const u8{ - "tmp.zig:2:21: error: expected expression, found 'invalid'", + "tmp.zig:2:21: error: expected expression, found 'invalid bytes'", "tmp.zig:2:28: note: invalid byte: ';'", }); @@ -2171,7 +2171,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = x; \\} , &[_][]const u8{ - "tmp.zig:3:6: error: expected ',' after field", + "tmp.zig:3:7: error: expected ',' after field", }); ctx.objErrStage1("bad alignment type", @@ -5733,7 +5733,7 @@ pub fn addCases(ctx: *TestContext) !void { \\const foo = "a \\b"; , &[_][]const u8{ - "tmp.zig:1:13: error: expected expression, found 'invalid'", + "tmp.zig:1:13: error: expected expression, found 'invalid bytes'", "tmp.zig:1:15: note: invalid byte: '\\n'", }); @@ -7638,7 +7638,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ const a = '\U1234'; \\} , &[_][]const u8{ - "tmp.zig:2:15: error: expected expression, found 'invalid'", + "tmp.zig:2:15: error: expected expression, found 'invalid bytes'", "tmp.zig:2:18: note: invalid byte: '1'", }); @@ -7654,7 +7654,7 @@ pub fn addCases(ctx: *TestContext) !void { "fn foo() bool {\r\n" ++ " return true;\r\n" ++ "}\r\n", &[_][]const u8{ - "tmp.zig:1:1: error: expected test, comptime, var decl, or container field, found 'invalid'", + "tmp.zig:1:1: error: expected test, comptime, var decl, or container field, found 'invalid bytes'", "tmp.zig:1:1: note: invalid byte: '\\xff'", }); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 949a7eb6b7..ae810a31ca 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -693,7 +693,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = E1.a; \\} , &.{ - ":3:6: error: expected ',' after field", + ":3:7: error: expected ',' after field", }); // Redundant non-exhaustive enum mark. From 35e989235b29c5d921c8cf053a1d2f92fa0ce57a Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Wed, 16 Feb 2022 13:19:43 +0200 Subject: [PATCH 2/5] parser: get rid of "invalid token" error --- lib/std/zig/Ast.zig | 6 ------ lib/std/zig/parse.zig | 11 ++++++----- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 64549d3cf6..3282497db4 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -291,11 +291,6 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { .invalid_bit_range => { return stream.writeAll("bit range not allowed on slices and arrays"); }, - .invalid_token => { - return stream.print("invalid token: '{s}'", .{ - token_tags[parse_error.token].symbol(), - }); - }, .same_line_doc_comment => { return stream.writeAll("same line documentation comment"); }, @@ -2515,7 +2510,6 @@ pub const Error = struct { extra_volatile_qualifier, ptr_mod_on_array_child_type, invalid_bit_range, - invalid_token, same_line_doc_comment, unattached_doc_comment, varargs_nonfinal, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index bc3c2a35b3..313b876278 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -1437,7 +1437,8 @@ const Parser = struct { } const rhs = try p.parseExprPrecedence(info.prec + 1); if (rhs == 0) { - return p.fail(.invalid_token); + try p.warn(.expected_expr); + return node; } node = try p.addNode(.{ @@ -1916,7 +1917,7 @@ const Parser = struct { /// IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)? fn parseIfExpr(p: *Parser) !Node.Index { - return p.parseIf(parseExpr); + return p.parseIf(expectExpr); } /// Block <- LBRACE Statement* RBRACE @@ -2384,7 +2385,7 @@ const Parser = struct { .builtin => return p.parseBuiltinCall(), .keyword_fn => return p.parseFnProto(), - .keyword_if => return p.parseIf(parseTypeExpr), + .keyword_if => return p.parseIf(expectTypeExpr), .keyword_switch => return p.expectSwitchExpr(), .keyword_extern, @@ -3577,7 +3578,7 @@ const Parser = struct { _ = try p.parsePtrPayload(); const then_expr = try bodyParseFn(p); - if (then_expr == 0) return p.fail(.invalid_token); + assert(then_expr != 0); _ = p.eatToken(.keyword_else) orelse return p.addNode(.{ .tag = .if_simple, @@ -3589,7 +3590,7 @@ const Parser = struct { }); _ = try p.parsePayload(); const else_expr = try bodyParseFn(p); - if (else_expr == 0) return p.fail(.invalid_token); + assert(then_expr != 0); return p.addNode(.{ .tag = .@"if", From c9dde10f8629c3ad2234f6220990b1dd70bac807 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 17 Feb 2022 12:50:36 +0200 Subject: [PATCH 3/5] stage1: improve error message when casting tuples --- doc/langref.html.in | 2 +- src/stage1/ir.cpp | 31 +++++++++++++++++++++++++++---- test/compile_errors.zig | 19 ++++++++++++------- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 24b976ee42..491e7f8f6c 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -10405,7 +10405,7 @@ pub fn main() !void {

String literals such as {#syntax#}"foo"{#endsyntax#} are in the global constant data section. This is why it is an error to pass a string literal to a mutable slice, like this:

- {#code_begin|test_err|expected type '[]u8'#} + {#code_begin|test_err|cannot cast pointer to array literal to slice type '[]u8'#} fn foo(s: []u8) void { _ = s; } diff --git a/src/stage1/ir.cpp b/src/stage1/ir.cpp index 63466849a4..3ac1ddb51c 100644 --- a/src/stage1/ir.cpp +++ b/src/stage1/ir.cpp @@ -7843,7 +7843,7 @@ static Stage1AirInst *ir_analyze_cast(IrAnalyze *ira, Scope *scope, AstNode *sou bool const_ok = (slice_ptr_type->data.pointer.is_const || array_type->data.array.len == 0 || !actual_type->data.pointer.is_const); - if (const_ok && types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, + if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type, array_type->data.array.child_type, source_node, !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk && (slice_ptr_type->data.pointer.sentinel == nullptr || @@ -7851,6 +7851,14 @@ static Stage1AirInst *ir_analyze_cast(IrAnalyze *ira, Scope *scope, AstNode *sou const_values_equal(ira->codegen, array_type->data.array.sentinel, slice_ptr_type->data.pointer.sentinel)))) { + if (!const_ok) { + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("cannot cast pointer to array literal to slice type '%s'", + buf_ptr(&wanted_type->name))); + add_error_note(ira->codegen, msg, source_node, + buf_sprintf("cast discards const qualifier")); + return ira->codegen->invalid_inst_gen; + } // If the pointers both have ABI align, it works. // Or if the array length is 0, alignment doesn't matter. bool ok_align = array_type->data.array.len == 0 || @@ -8208,8 +8216,16 @@ static Stage1AirInst *ir_analyze_cast(IrAnalyze *ira, Scope *scope, AstNode *sou ZigType *wanted_child = wanted_type->data.pointer.child_type; bool const_ok = (!actual_type->data.pointer.is_const || wanted_type->data.pointer.is_const); if (wanted_child->id == ZigTypeIdArray && (is_array_init || field_count == 0) && - wanted_child->data.array.len == field_count && (const_ok || field_count == 0)) + wanted_child->data.array.len == field_count) { + if (!const_ok && field_count != 0) { + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("cannot cast pointer to array literal to '%s'", + buf_ptr(&wanted_type->name))); + add_error_note(ira->codegen, msg, source_node, + buf_sprintf("cast discards const qualifier")); + return ira->codegen->invalid_inst_gen; + } Stage1AirInst *res = ir_analyze_struct_literal_to_array(ira, scope, source_node, value, anon_type, wanted_child); if (res->value->type->id == ZigTypeIdPointer) return res; @@ -8241,6 +8257,13 @@ static Stage1AirInst *ir_analyze_cast(IrAnalyze *ira, Scope *scope, AstNode *sou res = ir_get_ref(ira, scope, source_node, res, actual_type->data.pointer.is_const, actual_type->data.pointer.is_volatile); return ir_resolve_ptr_of_array_to_slice(ira, scope, source_node, res, wanted_type, nullptr); + } else if (!slice_type->data.pointer.is_const && actual_type->data.pointer.is_const && field_count != 0) { + ErrorMsg *msg = ir_add_error_node(ira, source_node, + buf_sprintf("cannot cast pointer to array literal to slice type '%s'", + buf_ptr(&wanted_type->name))); + add_error_note(ira->codegen, msg, source_node, + buf_sprintf("cast discards const qualifier")); + return ira->codegen->invalid_inst_gen; } } } @@ -15068,7 +15091,7 @@ static Stage1AirInst *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, Stage1ZirI return ira->codegen->invalid_inst_gen; if (actual_array_type->id != ZigTypeIdArray) { ir_add_error_node(ira, elem_ptr_instruction->init_array_type_source_node, - buf_sprintf("array literal requires address-of operator to coerce to slice type '%s'", + buf_sprintf("array literal requires address-of operator (&) to coerce to slice type '%s'", buf_ptr(&actual_array_type->name))); return ira->codegen->invalid_inst_gen; } @@ -17473,7 +17496,7 @@ static Stage1AirInst *ir_analyze_instruction_container_init_list(IrAnalyze *ira, if (is_slice(container_type)) { ir_add_error_node(ira, instruction->init_array_type_source_node, - buf_sprintf("array literal requires address-of operator to coerce to slice type '%s'", + buf_sprintf("array literal requires address-of operator (&) to coerce to slice type '%s'", buf_ptr(&container_type->name))); return ira->codegen->invalid_inst_gen; } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index faed747a61..967ec1e9ec 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -86,9 +86,12 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = c; \\} , &[_][]const u8{ - "tmp.zig:2:31: error: expected type '[][]const u8', found '*const struct:2:31'", - "tmp.zig:6:33: error: expected type '*[2][]const u8', found '*const struct:6:33'", + "tmp.zig:2:31: error: cannot cast pointer to array literal to slice type '[][]const u8'", + "tmp.zig:2:31: note: cast discards const qualifier", + "tmp.zig:6:33: error: cannot cast pointer to array literal to '*[2][]const u8'", + "tmp.zig:6:33: note: cast discards const qualifier", "tmp.zig:11:21: error: expected type '*S', found '*const struct:11:21'", + "tmp.zig:11:21: note: cast discards const qualifier", }); ctx.objErrStage1("@Type() union payload is undefined", @@ -1962,7 +1965,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = geo_data; \\} , &[_][]const u8{ - "tmp.zig:4:30: error: array literal requires address-of operator to coerce to slice type '[][2]f32'", + "tmp.zig:4:30: error: array literal requires address-of operator (&) to coerce to slice type '[][2]f32'", }); ctx.objErrStage1("slicing of global undefined pointer", @@ -2537,7 +2540,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = x; \\} , &[_][]const u8{ - "tmp.zig:2:15: error: array literal requires address-of operator to coerce to slice type '[]u8'", + "tmp.zig:2:15: error: array literal requires address-of operator (&) to coerce to slice type '[]u8'", }); ctx.objErrStage1("slice passed as array init type", @@ -2546,7 +2549,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = x; \\} , &[_][]const u8{ - "tmp.zig:2:15: error: array literal requires address-of operator to coerce to slice type '[]u8'", + "tmp.zig:2:15: error: array literal requires address-of operator (&) to coerce to slice type '[]u8'", }); ctx.objErrStage1("inferred array size invalid here", @@ -3493,7 +3496,8 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = sliceA; \\} , &[_][]const u8{ - "tmp.zig:3:27: error: expected type '[]u8', found '*const [1]u8'", + "tmp.zig:3:27: error: cannot cast pointer to array literal to slice type '[]u8'", + "tmp.zig:3:27: note: cast discards const qualifier", }); ctx.objErrStage1("deref slice and get len field", @@ -8717,7 +8721,8 @@ pub fn addCases(ctx: *TestContext) !void { \\ comptime ignore(@typeInfo(MyStruct).Struct.fields[0]); \\} , &[_][]const u8{ - ":5:28: error: expected type '[]u8', found '*const [3:0]u8'", + ":5:28: error: cannot cast pointer to array literal to slice type '[]u8'", + ":5:28: note: cast discards const qualifier", }); ctx.objErrStage1("integer underflow error", From 92f276781417d7e710081470d97606e26cf764d6 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 17 Feb 2022 14:22:34 +0200 Subject: [PATCH 4/5] parser: add error for missing colon before continue expr If a '(' is found where the continue expression was expected and it is on the same line as the previous token issue an error about missing colon before the continue expression. --- lib/std/zig/Ast.zig | 7 +++++-- lib/std/zig/parse.zig | 7 ++++++- lib/std/zig/parser_test.zig | 19 +++++++++++++++++++ test/compile_errors.zig | 4 ++-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 3282497db4..5501352084 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -300,6 +300,9 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { .varargs_nonfinal => { return stream.writeAll("function prototype has parameter after varargs"); }, + .expected_continue_expr => { + return stream.writeAll("expected ':' before while continue expression"); + }, .expected_semi_after_decl => { return stream.writeAll("expected ';' after declaration"); @@ -2467,6 +2470,7 @@ pub const full = struct { pub const Error = struct { tag: Tag, + /// True if `token` points to the token before the token causing an issue. token_is_prev: bool = false, token: TokenIndex, extra: union { @@ -2513,8 +2517,7 @@ pub const Error = struct { same_line_doc_comment, unattached_doc_comment, varargs_nonfinal, - - // these have `token` set to token after which a semicolon was expected + expected_continue_expr, expected_semi_after_decl, expected_semi_after_stmt, expected_comma_after_field, diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 313b876278..46d2b0f49e 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -2943,7 +2943,12 @@ const Parser = struct { /// WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN fn parseWhileContinueExpr(p: *Parser) !Node.Index { - _ = p.eatToken(.colon) orelse return null_node; + _ = p.eatToken(.colon) orelse { + if (p.token_tags[p.tok_i] == .l_paren and + p.tokensOnSameLine(p.tok_i - 1, p.tok_i)) + return p.fail(.expected_continue_expr); + return null_node; + }; _ = try p.expectToken(.l_paren); const node = try p.parseAssignExpr(); if (node == 0) return p.fail(.expected_expr_or_assignment); diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index e5932ddfc3..9bf7ae8da9 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -5018,6 +5018,25 @@ test "zig fmt: make single-line if no trailing comma" { ); } +test "zig fmt: while continue expr" { + try testCanonical( + \\test { + \\ while (i > 0) + \\ (i * 2); + \\} + \\ + ); + try testError( + \\test { + \\ while (i > 0) (i -= 1) { + \\ print("test123", .{}); + \\ } + \\} + , &[_]Error{ + .expected_continue_expr, + }); +} + test "zig fmt: error for invalid bit range" { try testError( \\var x: []align(0:0:0)u8 = bar; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 967ec1e9ec..650f54ae3f 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -4869,11 +4869,11 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn entry() void { \\ while(true) {} \\ var good = {}; - \\ while(true) ({}) + \\ while(true) 1 \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:4:21: error: expected ';' or 'else' after statement", + "tmp.zig:4:18: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - while expression", From 6b65590715d0871c11635fc49cb1fc471a60ea59 Mon Sep 17 00:00:00 2001 From: Veikka Tuominen Date: Thu, 17 Feb 2022 22:14:45 +0200 Subject: [PATCH 5/5] parser: add notes to decl_between_fields error --- lib/std/zig/Ast.zig | 11 +++ lib/std/zig/parse.zig | 25 +++++++ lib/std/zig/parser_test.zig | 2 + src/Module.zig | 11 +++ src/main.zig | 137 ++++++++++++++++++++---------------- test/compile_errors.zig | 2 + 6 files changed, 129 insertions(+), 59 deletions(-) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 5501352084..a68959837a 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -329,6 +329,13 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { return stream.writeAll("expected field initializer"); }, + .previous_field => { + return stream.writeAll("field before declarations here"); + }, + .next_field => { + return stream.writeAll("field after declarations here"); + }, + .expected_token => { const found_tag = token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)]; const expected_symbol = parse_error.extra.expected_tag.symbol(); @@ -2470,6 +2477,7 @@ pub const full = struct { pub const Error = struct { tag: Tag, + is_note: bool = false, /// True if `token` points to the token before the token causing an issue. token_is_prev: bool = false, token: TokenIndex, @@ -2527,6 +2535,9 @@ pub const Error = struct { expected_comma_after_switch_prong, expected_initializer, + previous_field, + next_field, + /// `expected_tag` is populated. expected_token, }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index 46d2b0f49e..2578629af5 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -91,6 +91,9 @@ const Parser = struct { extra_data: std.ArrayListUnmanaged(Node.Index), scratch: std.ArrayListUnmanaged(Node.Index), + /// Used for the error note of decl_between_fields error. + last_field: TokenIndex = undefined, + const SmallSpan = union(enum) { zero_or_one: Node.Index, multi: Node.SubRange, @@ -270,6 +273,8 @@ const Parser = struct { .keyword_comptime => switch (p.token_tags[p.tok_i + 1]) { .identifier => { p.tok_i += 1; + const identifier = p.tok_i; + defer p.last_field = identifier; const container_field = try p.expectContainerFieldRecoverable(); if (container_field != 0) { switch (field_state) { @@ -280,6 +285,16 @@ const Parser = struct { .tag = .decl_between_fields, .token = p.nodes.items(.main_token)[node], }); + try p.warnMsg(.{ + .tag = .previous_field, + .is_note = true, + .token = p.last_field, + }); + try p.warnMsg(.{ + .tag = .next_field, + .is_note = true, + .token = identifier, + }); // Continue parsing; error will be reported later. field_state = .err; }, @@ -373,6 +388,8 @@ const Parser = struct { trailing = p.token_tags[p.tok_i - 1] == .semicolon; }, .identifier => { + const identifier = p.tok_i; + defer p.last_field = identifier; const container_field = try p.expectContainerFieldRecoverable(); if (container_field != 0) { switch (field_state) { @@ -383,6 +400,14 @@ const Parser = struct { .tag = .decl_between_fields, .token = p.nodes.items(.main_token)[node], }); + try p.warnMsg(.{ + .tag = .previous_field, + .token = p.last_field, + }); + try p.warnMsg(.{ + .tag = .next_field, + .token = identifier, + }); // Continue parsing; error will be reported later. field_state = .err; }, diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 9bf7ae8da9..0c79b3b187 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -226,6 +226,8 @@ test "zig fmt: decl between fields" { \\}; , &[_]Error{ .decl_between_fields, + .previous_field, + .next_field, }); } diff --git a/src/Module.zig b/src/Module.zig index 280c58ade8..62b992b546 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -3014,6 +3014,17 @@ pub fn astGenFile(mod: *Module, file: *File) !void { .parent_decl_node = 0, .lazy = .{ .byte_abs = byte_abs }, }, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])}); + } else if (parse_err.tag == .decl_between_fields) { + try mod.errNoteNonLazy(.{ + .file_scope = file, + .parent_decl_node = 0, + .lazy = .{ .byte_abs = token_starts[file.tree.errors[1].token] }, + }, err_msg, "field before declarations here", .{}); + try mod.errNoteNonLazy(.{ + .file_scope = file, + .parent_decl_node = 0, + .lazy = .{ .byte_abs = token_starts[file.tree.errors[2].token] }, + }, err_msg, "field after declarations here", .{}); } { diff --git a/src/main.zig b/src/main.zig index 3d33268718..4a934bdff5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3769,9 +3769,7 @@ pub fn cmdFmt(gpa: Allocator, arena: Allocator, args: []const []const u8) !void }; defer tree.deinit(gpa); - for (tree.errors) |parse_error| { - try printErrMsgToStdErr(gpa, arena, parse_error, tree, "", color); - } + try printErrsMsgToStdErr(gpa, arena, tree.errors, tree, "", color); var has_ast_error = false; if (check_ast_flag) { const Module = @import("Module.zig"); @@ -3959,9 +3957,7 @@ fn fmtPathFile( var tree = try std.zig.parse(fmt.gpa, source_code); defer tree.deinit(fmt.gpa); - for (tree.errors) |parse_error| { - try printErrMsgToStdErr(fmt.gpa, fmt.arena, parse_error, tree, file_path, fmt.color); - } + try printErrsMsgToStdErr(fmt.gpa, fmt.arena, tree.errors, tree, file_path, fmt.color); if (tree.errors.len != 0) { fmt.any_error = true; return; @@ -4041,66 +4037,95 @@ fn fmtPathFile( } } -fn printErrMsgToStdErr( +fn printErrsMsgToStdErr( gpa: mem.Allocator, arena: mem.Allocator, - parse_error: Ast.Error, + parse_errors: []const Ast.Error, tree: Ast, path: []const u8, color: Color, ) !void { - const lok_token = parse_error.token; - const token_tags = tree.tokens.items(.tag); - const start_loc = tree.tokenLocation(0, lok_token); - const source_line = tree.source[start_loc.line_start..start_loc.line_end]; + var i: usize = 0; + while (i < parse_errors.len) : (i += 1) { + const parse_error = parse_errors[i]; + const lok_token = parse_error.token; + const token_tags = tree.tokens.items(.tag); + const start_loc = tree.tokenLocation(0, lok_token); + const source_line = tree.source[start_loc.line_start..start_loc.line_end]; - var text_buf = std.ArrayList(u8).init(gpa); - defer text_buf.deinit(); - const writer = text_buf.writer(); - try tree.renderError(parse_error, writer); - const text = text_buf.items; + var text_buf = std.ArrayList(u8).init(gpa); + defer text_buf.deinit(); + const writer = text_buf.writer(); + try tree.renderError(parse_error, writer); + const text = text_buf.items; - var notes_buffer: [1]Compilation.AllErrors.Message = undefined; - var notes_len: usize = 0; + var notes_buffer: [2]Compilation.AllErrors.Message = undefined; + var notes_len: usize = 0; - if (token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)] == .invalid) { - const bad_off = @intCast(u32, tree.tokenSlice(parse_error.token + @boolToInt(parse_error.token_is_prev)).len); - const byte_offset = @intCast(u32, start_loc.line_start) + @intCast(u32, start_loc.column) + bad_off; - notes_buffer[notes_len] = .{ + if (token_tags[parse_error.token + @boolToInt(parse_error.token_is_prev)] == .invalid) { + const bad_off = @intCast(u32, tree.tokenSlice(parse_error.token + @boolToInt(parse_error.token_is_prev)).len); + const byte_offset = @intCast(u32, start_loc.line_start) + @intCast(u32, start_loc.column) + bad_off; + notes_buffer[notes_len] = .{ + .src = .{ + .src_path = path, + .msg = try std.fmt.allocPrint(arena, "invalid byte: '{'}'", .{ + std.zig.fmtEscapes(tree.source[byte_offset..][0..1]), + }), + .byte_offset = byte_offset, + .line = @intCast(u32, start_loc.line), + .column = @intCast(u32, start_loc.column) + bad_off, + .source_line = source_line, + }, + }; + notes_len += 1; + } else if (parse_error.tag == .decl_between_fields) { + const prev_loc = tree.tokenLocation(0, parse_errors[i + 1].token); + notes_buffer[0] = .{ + .src = .{ + .src_path = path, + .msg = "field before declarations here", + .byte_offset = @intCast(u32, prev_loc.line_start), + .line = @intCast(u32, prev_loc.line), + .column = @intCast(u32, prev_loc.column), + .source_line = tree.source[prev_loc.line_start..prev_loc.line_end], + }, + }; + const next_loc = tree.tokenLocation(0, parse_errors[i + 2].token); + notes_buffer[1] = .{ + .src = .{ + .src_path = path, + .msg = "field after declarations here", + .byte_offset = @intCast(u32, next_loc.line_start), + .line = @intCast(u32, next_loc.line), + .column = @intCast(u32, next_loc.column), + .source_line = tree.source[next_loc.line_start..next_loc.line_end], + }, + }; + notes_len = 2; + i += 2; + } + + const extra_offset = tree.errorOffset(parse_error); + const message: Compilation.AllErrors.Message = .{ .src = .{ .src_path = path, - .msg = try std.fmt.allocPrint(arena, "invalid byte: '{'}'", .{ - std.zig.fmtEscapes(tree.source[byte_offset..][0..1]), - }), - .byte_offset = byte_offset, + .msg = text, + .byte_offset = @intCast(u32, start_loc.line_start) + extra_offset, .line = @intCast(u32, start_loc.line), - .column = @intCast(u32, start_loc.column) + bad_off, + .column = @intCast(u32, start_loc.column) + extra_offset, .source_line = source_line, + .notes = notes_buffer[0..notes_len], }, }; - notes_len += 1; + + const ttyconf: std.debug.TTY.Config = switch (color) { + .auto => std.debug.detectTTYConfig(), + .on => .escape_codes, + .off => .no_color, + }; + + message.renderToStdErr(ttyconf); } - - const extra_offset = tree.errorOffset(parse_error); - const message: Compilation.AllErrors.Message = .{ - .src = .{ - .src_path = path, - .msg = text, - .byte_offset = @intCast(u32, start_loc.line_start) + extra_offset, - .line = @intCast(u32, start_loc.line), - .column = @intCast(u32, start_loc.column) + extra_offset, - .source_line = source_line, - .notes = notes_buffer[0..notes_len], - }, - }; - - const ttyconf: std.debug.TTY.Config = switch (color) { - .auto => std.debug.detectTTYConfig(), - .on => .escape_codes, - .off => .no_color, - }; - - message.renderToStdErr(ttyconf); } pub const info_zen = @@ -4658,9 +4683,7 @@ pub fn cmdAstCheck( file.tree_loaded = true; defer file.tree.deinit(gpa); - for (file.tree.errors) |parse_error| { - try printErrMsgToStdErr(gpa, arena, parse_error, file.tree, file.sub_file_path, color); - } + try printErrsMsgToStdErr(gpa, arena, file.tree.errors, file.tree, file.sub_file_path, color); if (file.tree.errors.len != 0) { process.exit(1); } @@ -4786,9 +4809,7 @@ pub fn cmdChangelist( file.tree_loaded = true; defer file.tree.deinit(gpa); - for (file.tree.errors) |parse_error| { - try printErrMsgToStdErr(gpa, arena, parse_error, file.tree, old_source_file, .auto); - } + try printErrsMsgToStdErr(gpa, arena, file.tree.errors, file.tree, old_source_file, .auto); if (file.tree.errors.len != 0) { process.exit(1); } @@ -4825,9 +4846,7 @@ pub fn cmdChangelist( var new_tree = try std.zig.parse(gpa, new_source); defer new_tree.deinit(gpa); - for (new_tree.errors) |parse_error| { - try printErrMsgToStdErr(gpa, arena, parse_error, new_tree, new_source_file, .auto); - } + try printErrsMsgToStdErr(gpa, arena, new_tree.errors, new_tree, new_source_file, .auto); if (new_tree.errors.len != 0) { process.exit(1); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 650f54ae3f..f982d8298a 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -877,6 +877,8 @@ pub fn addCases(ctx: *TestContext) !void { \\} , &[_][]const u8{ "tmp.zig:6:5: error: declarations are not allowed between container fields", + "tmp.zig:5:5: note: field before declarations here", + "tmp.zig:9:5: note: field after declarations here", }); ctx.objErrStage1("non-extern function with var args",