Merge pull request #10879 from Vexu/err

make some errors point to the end of the previous token
This commit is contained in:
Andrew Kelley 2022-02-13 16:15:00 -05:00 committed by GitHub
commit f73044dae5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 137 additions and 81 deletions

View File

@ -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,
};

View File

@ -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;

View File

@ -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(),
};

View File

@ -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],
},

View File

@ -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",

View File

@ -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.