diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index a756834c58..cecdc3adeb 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -64,6 +64,24 @@ pub fn renderToArrayList(tree: Ast, buffer: *std.ArrayList(u8)) RenderError!void return @import("./render.zig").renderTree(buffer, tree); } +/// 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 tokenLocation(self: Ast, start_offset: ByteOffset, token_index: TokenIndex) Location { var loc = Location{ .line = 0, @@ -216,14 +234,10 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { }); }, .expected_semi_or_else => { - return stream.print("expected ';' or 'else', found '{s}'", .{ - token_tags[parse_error.token].symbol(), - }); + return stream.writeAll("expected ';' or 'else' after statement"); }, .expected_semi_or_lbrace => { - return stream.print("expected ';' or '{{', found '{s}'", .{ - token_tags[parse_error.token].symbol(), - }); + return stream.writeAll("expected ';' or block after function prototype"); }, .expected_statement => { return stream.print("expected statement, found '{s}'", .{ @@ -306,6 +320,28 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void { return stream.writeAll("function prototype has parameter after varargs"); }, + .expected_semi_after_decl => { + return stream.writeAll("expected ';' after declaration"); + }, + .expected_semi_after_stmt => { + return stream.writeAll("expected ';' after statement"); + }, + .expected_comma_after_field => { + return stream.writeAll("expected ',' after field"); + }, + .expected_comma_after_arg => { + return stream.writeAll("expected ',' after argument"); + }, + .expected_comma_after_param => { + return stream.writeAll("expected ',' after parameter"); + }, + .expected_comma_after_initializer => { + return stream.writeAll("expected ',' after initializer"); + }, + .expected_comma_after_switch_prong => { + return stream.writeAll("expected ',' after switch prong"); + }, + .expected_token => { const found_tag = token_tags[parse_error.token]; const expected_symbol = parse_error.extra.expected_tag.symbol(); @@ -2495,6 +2531,15 @@ pub const Error = struct { unattached_doc_comment, varargs_nonfinal, + // these have `token` set to token after which a semicolon was expected + 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_tag` is populated. expected_token, }; diff --git a/lib/std/zig/parse.zig b/lib/std/zig/parse.zig index cf27e6b1c7..9050ba1bc4 100644 --- a/lib/std/zig/parse.zig +++ b/lib/std/zig/parse.zig @@ -160,6 +160,12 @@ const Parser = struct { .extra = .{ .expected_tag = expected_token }, }); } + + fn warnExpectedAfter(p: *Parser, error_tag: AstError.Tag) error{OutOfMemory}!void { + @setCold(true); + try p.warnMsg(.{ .tag = error_tag, .token = p.tok_i - 1 }); + } + fn warnMsg(p: *Parser, msg: Ast.Error) error{OutOfMemory}!void { @setCold(true); try p.errors.append(p.gpa, msg); @@ -258,7 +264,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.warnExpected(.comma); + try p.warnExpectedAfter(.expected_comma_after_field); p.findNextContainerMember(); } }, @@ -361,7 +367,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.warnExpected(.comma); + try p.warnExpectedAfter(.expected_comma_after_field); p.findNextContainerMember(); } }, @@ -579,7 +585,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.warn(.expected_semi_or_lbrace); + try p.warnExpectedAfter(.expected_semi_or_lbrace); return null_node; }, } @@ -592,7 +598,7 @@ const Parser = struct { const thread_local_token = p.eatToken(.keyword_threadlocal); const var_decl = try p.parseVarDecl(); if (var_decl != 0) { - _ = try p.expectToken(.semicolon); + try p.expectSemicolon(.expected_semi_after_decl, false); return var_decl; } if (thread_local_token != null) { @@ -620,7 +626,7 @@ const Parser = struct { fn expectUsingNamespace(p: *Parser) !Node.Index { const usingnamespace_token = p.assertToken(.keyword_usingnamespace); const expr = try p.expectExpr(); - _ = try p.expectToken(.semicolon); + try p.expectSemicolon(.expected_semi_after_decl, false); return p.addNode(.{ .tag = .@"usingnamespace", .main_token = usingnamespace_token, @@ -857,7 +863,7 @@ const Parser = struct { const var_decl = try p.parseVarDecl(); if (var_decl != 0) { - _ = try p.expectTokenRecoverable(.semicolon); + try p.expectSemicolon(.expected_semi_after_decl, true); return var_decl; } @@ -921,7 +927,7 @@ const Parser = struct { const assign_expr = try p.parseAssignExpr(); if (assign_expr != 0) { - _ = try p.expectTokenRecoverable(.semicolon); + try p.expectSemicolon(.expected_semi_after_stmt, true); return assign_expr; } @@ -990,7 +996,7 @@ const Parser = struct { }; _ = p.eatToken(.keyword_else) orelse { if (else_required) { - try p.warn(.expected_semi_or_else); + try p.warnExpectedAfter(.expected_semi_or_else); } return p.addNode(.{ .tag = .if_simple, @@ -1085,7 +1091,7 @@ const Parser = struct { }; _ = p.eatToken(.keyword_else) orelse { if (else_required) { - try p.warn(.expected_semi_or_else); + try p.warnExpectedAfter(.expected_semi_or_else); } return p.addNode(.{ .tag = .for_simple, @@ -1160,7 +1166,7 @@ const Parser = struct { }; _ = p.eatToken(.keyword_else) orelse { if (else_required) { - try p.warn(.expected_semi_or_else); + try p.warnExpectedAfter(.expected_semi_or_else); } if (cont_expr == 0) { return p.addNode(.{ @@ -1211,7 +1217,7 @@ const Parser = struct { } const assign_expr = try p.parseAssignExpr(); if (assign_expr != 0) { - _ = try p.expectTokenRecoverable(.semicolon); + try p.expectSemicolon(.expected_semi_after_stmt, true); return assign_expr; } return null_node; @@ -2044,7 +2050,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_initializer), } if (p.eatToken(.r_brace)) |_| break; const next = try p.expectFieldInit(); @@ -2085,7 +2091,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_initializer), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2164,7 +2170,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_arg), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2220,7 +2226,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_arg), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2461,7 +2467,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_initializer), } if (p.eatToken(.r_brace)) |_| break; const next = try p.expectFieldInit(); @@ -2513,7 +2519,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_initializer), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -2574,7 +2580,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_field), } } return p.addNode(.{ @@ -3229,6 +3235,10 @@ const Parser = struct { .rhs = p.nextToken(), }, }), + .l_brace => { + // this a misplaced `.{`, handle the error somewhere else + return null_node; + }, else => { p.tok_i += 1; try p.warn(.expected_suffix_op); @@ -3389,7 +3399,24 @@ const Parser = struct { /// SwitchProngList <- (SwitchProng COMMA)* SwitchProng? fn parseSwitchProngList(p: *Parser) !Node.SubRange { - return ListParseFn(parseSwitchProng)(p); + const scratch_top = p.scratch.items.len; + defer p.scratch.shrinkRetainingCapacity(scratch_top); + + while (true) { + const item = try parseSwitchProng(p); + if (item == 0) break; + + try p.scratch.append(p.gpa, item); + + switch (p.token_tags[p.tok_i]) { + .comma => p.tok_i += 1, + // 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), + } + } + return p.listToSpan(p.scratch.items[scratch_top..]); } /// ParamDeclList <- (ParamDecl COMMA)* ParamDecl? @@ -3415,7 +3442,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.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_param), } } if (varargs == .nonfinal) { @@ -3429,33 +3456,6 @@ const Parser = struct { }; } - const NodeParseFn = fn (p: *Parser) Error!Node.Index; - - fn ListParseFn(comptime nodeParseFn: anytype) (fn (p: *Parser) Error!Node.SubRange) { - return struct { - pub fn parse(p: *Parser) Error!Node.SubRange { - const scratch_top = p.scratch.items.len; - defer p.scratch.shrinkRetainingCapacity(scratch_top); - - while (true) { - const item = try nodeParseFn(p); - if (item == 0) break; - - try p.scratch.append(p.gpa, item); - - switch (p.token_tags[p.tok_i]) { - .comma => p.tok_i += 1, - // All possible delimiters. - .colon, .r_paren, .r_brace, .r_bracket => break, - // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpected(.comma), - } - } - return p.listToSpan(p.scratch.items[scratch_top..]); - } - }.parse; - } - /// FnCallArguments <- LPAREN ExprList RPAREN /// ExprList <- (Expr COMMA)* Expr? fn parseBuiltinCall(p: *Parser) !Node.Index { @@ -3486,7 +3486,7 @@ const Parser = struct { break; }, // Likely just a missing comma; give error but continue parsing. - else => try p.warnExpected(.comma), + else => try p.warnExpectedAfter(.expected_comma_after_arg), } } const comma = (p.token_tags[p.tok_i - 2] == .comma); @@ -3582,7 +3582,7 @@ const Parser = struct { } /// KEYWORD_if LPAREN Expr RPAREN PtrPayload? Body (KEYWORD_else Payload? Body)? - fn parseIf(p: *Parser, bodyParseFn: NodeParseFn) !Node.Index { + fn parseIf(p: *Parser, bodyParseFn: fn (p: *Parser) Error!Node.Index) !Node.Index { const if_token = p.eatToken(.keyword_if) orelse return null_node; _ = try p.expectToken(.l_paren); const condition = try p.expectExpr(); @@ -3670,6 +3670,15 @@ const Parser = struct { } } + fn expectSemicolon(p: *Parser, error_tag: AstError.Tag, recoverable: bool) Error!void { + if (p.token_tags[p.tok_i] == .semicolon) { + _ = p.nextToken(); + return; + } + try p.warnExpectedAfter(error_tag); + if (!recoverable) return error.ParseError; + } + fn nextToken(p: *Parser) TokenIndex { const result = p.tok_i; p.tok_i += 1; diff --git a/src/Module.zig b/src/Module.zig index b9e50355fd..4ffd6925b6 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -2995,13 +2995,14 @@ 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); try file.tree.renderError(parse_err, msg.writer()); const err_msg = try gpa.create(ErrorMsg); err_msg.* = .{ .src_loc = .{ .file_scope = file, .parent_decl_node = 0, - .lazy = .{ .byte_abs = token_starts[parse_err.token] }, + .lazy = .{ .byte_abs = token_starts[parse_err.token] + extra_offset }, }, .msg = msg.toOwnedSlice(), }; diff --git a/src/main.zig b/src/main.zig index 75655d6a2a..12e9f88088 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4040,13 +4040,14 @@ fn printErrMsgToStdErr( notes_len += 1; } + const extra_offset = tree.errorOffset(parse_error.tag, parse_error.token); const message: Compilation.AllErrors.Message = .{ .src = .{ .src_path = path, .msg = text, - .byte_offset = @intCast(u32, start_loc.line_start), + .byte_offset = @intCast(u32, start_loc.line_start) + extra_offset, .line = @intCast(u32, start_loc.line), - .column = @intCast(u32, start_loc.column), + .column = @intCast(u32, start_loc.column) + extra_offset, .source_line = source_line, .notes = notes_buffer[0..notes_len], }, diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 3c224013c9..79c17b4336 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2171,7 +2171,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = x; \\} , &[_][]const u8{ - "tmp.zig:3:7: error: expected ',', found 'align'", + "tmp.zig:3:6: error: expected ',' after field", }); ctx.objErrStage1("bad alignment type", @@ -4704,7 +4704,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:9: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - block expr", @@ -4715,7 +4715,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:11: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - comptime statement", @@ -4726,7 +4726,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:18: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - comptime expression", @@ -4737,7 +4737,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:20: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - defer", @@ -4748,7 +4748,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:15: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - if statement", @@ -4759,7 +4759,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';' or 'else', found 'var'", + "tmp.zig:4:18: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - if expression", @@ -4770,7 +4770,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:20: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - if-else statement", @@ -4781,7 +4781,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:28: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - if-else expression", @@ -4792,7 +4792,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:28: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - if-else-if statement", @@ -4803,7 +4803,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';' or 'else', found 'var'", + "tmp.zig:4:37: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - if-else-if expression", @@ -4814,7 +4814,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:37: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - if-else-if-else statement", @@ -4825,7 +4825,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:47: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - if-else-if-else expression", @@ -4836,7 +4836,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:45: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - test statement", @@ -4847,7 +4847,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';' or 'else', found 'var'", + "tmp.zig:4:24: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - test expression", @@ -4858,7 +4858,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:26: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - while statement", @@ -4869,7 +4869,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';' or 'else', found 'var'", + "tmp.zig:4:21: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - while expression", @@ -4880,7 +4880,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:23: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - while-continue statement", @@ -4891,7 +4891,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';' or 'else', found 'var'", + "tmp.zig:4:26: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - while-continue expression", @@ -4902,7 +4902,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:28: error: expected ';' after statement", }); ctx.objErrStage1("implicit semicolon - for statement", @@ -4913,7 +4913,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';' or 'else', found 'var'", + "tmp.zig:4:24: error: expected ';' or 'else' after statement", }); ctx.objErrStage1("implicit semicolon - for expression", @@ -4924,7 +4924,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ var bad = {}; \\} , &[_][]const u8{ - "tmp.zig:5:5: error: expected ';', found 'var'", + "tmp.zig:4:26: error: expected ';' after statement", }); ctx.objErrStage1("multiple function definitions", diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index cfb9831e40..949a7eb6b7 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -693,7 +693,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ _ = E1.a; \\} , &.{ - ":3:7: error: expected ',', found 'align'", + ":3:6: error: expected ',' after field", }); // Redundant non-exhaustive enum mark.