authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-12 19:37:02+03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-07-12 19:37:02+03:00
log7090f0471c0169c60a9476b537b09eebe1bdf6af
tree2410a74f83f6a5f7a20c30b2b606da419edf8e96
parent8033767082f2178416fba8cb4a4b03fef961d318
parent2a3f3766a437faed13736c1ff505854b6737ae33
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #12083 from Vexu/c-container-err

parser: add helpful error for C style container declarations

5 files changed, 121 insertions(+), 37 deletions(-)

lib/std/zig/Ast.zig+12
......@@ -334,6 +334,16 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
334334 .invalid_ampersand_ampersand => {
335335 return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");
336336 },
337 .c_style_container => {
338 return stream.print("'{s} {s}' is invalid", .{
339 parse_error.extra.expected_tag.symbol(), tree.tokenSlice(parse_error.token),
340 });
341 },
342 .zig_style_container => {
343 return stream.print("to declare a container do 'const {s} = {s}'", .{
344 tree.tokenSlice(parse_error.token), parse_error.extra.expected_tag.symbol(),
345 });
346 },
337347 .previous_field => {
338348 return stream.writeAll("field before declarations here");
339349 },
......@@ -2541,7 +2551,9 @@ pub const Error = struct {
25412551 expected_initializer,
25422552 mismatched_binary_op_whitespace,
25432553 invalid_ampersand_ampersand,
2554 c_style_container,
25442555
2556 zig_style_container,
25452557 previous_field,
25462558 next_field,
25472559
lib/std/zig/parse.zig+56-4
......@@ -178,7 +178,6 @@ const Parser = struct {
178178 .expected_block_or_assignment,
179179 .expected_block_or_expr,
180180 .expected_block_or_field,
181 .expected_container_members,
182181 .expected_expr,
183182 .expected_expr_or_assignment,
184183 .expected_fn,
......@@ -401,10 +400,12 @@ const Parser = struct {
401400 });
402401 try p.warnMsg(.{
403402 .tag = .previous_field,
403 .is_note = true,
404404 .token = last_field,
405405 });
406406 try p.warnMsg(.{
407407 .tag = .next_field,
408 .is_note = true,
408409 .token = identifier,
409410 });
410411 // Continue parsing; error will be reported later.
......@@ -440,9 +441,15 @@ const Parser = struct {
440441 break;
441442 },
442443 else => {
443 try p.warn(.expected_container_members);
444 // This was likely not supposed to end yet; try to find the next declaration.
445 p.findNextContainerMember();
444 const c_container = p.parseCStyleContainer() catch |err| switch (err) {
445 error.OutOfMemory => return error.OutOfMemory,
446 error.ParseError => false,
447 };
448 if (!c_container) {
449 try p.warn(.expected_container_members);
450 // This was likely not supposed to end yet; try to find the next declaration.
451 p.findNextContainerMember();
452 }
446453 },
447454 }
448455 }
......@@ -978,6 +985,20 @@ const Parser = struct {
978985 }),
979986 .keyword_switch => return p.expectSwitchExpr(),
980987 .keyword_if => return p.expectIfStatement(),
988 .keyword_enum, .keyword_struct, .keyword_union => {
989 const identifier = p.tok_i + 1;
990 if (try p.parseCStyleContainer()) {
991 // Return something so that `expectStatement` is happy.
992 return p.addNode(.{
993 .tag = .identifier,
994 .main_token = identifier,
995 .data = .{
996 .lhs = undefined,
997 .rhs = undefined,
998 },
999 });
1000 }
1001 },
9811002 else => {},
9821003 }
9831004
......@@ -3466,6 +3487,37 @@ const Parser = struct {
34663487 }
34673488 }
34683489
3490 /// Give a helpful error message for those transitioning from
3491 /// C's 'struct Foo {};' to Zig's 'const Foo = struct {};'.
3492 fn parseCStyleContainer(p: *Parser) Error!bool {
3493 const main_token = p.tok_i;
3494 switch (p.token_tags[p.tok_i]) {
3495 .keyword_enum, .keyword_union, .keyword_struct => {},
3496 else => return false,
3497 }
3498 const identifier = p.tok_i + 1;
3499 if (p.token_tags[identifier] != .identifier) return false;
3500 p.tok_i += 2;
3501
3502 try p.warnMsg(.{
3503 .tag = .c_style_container,
3504 .token = identifier,
3505 .extra = .{ .expected_tag = p.token_tags[main_token] },
3506 });
3507 try p.warnMsg(.{
3508 .tag = .zig_style_container,
3509 .is_note = true,
3510 .token = identifier,
3511 .extra = .{ .expected_tag = p.token_tags[main_token] },
3512 });
3513
3514 _ = try p.expectToken(.l_brace);
3515 _ = try p.parseContainerMembers();
3516 _ = try p.expectToken(.r_brace);
3517 try p.expectSemicolon(.expected_semi_after_decl, true);
3518 return true;
3519 }
3520
34693521 /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
34703522 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
34713523 fn parseByteAlign(p: *Parser) !Node.Index {
lib/std/zig/parser_test.zig+21
......@@ -212,6 +212,27 @@ test "zig fmt: top-level fields" {
212212 );
213213}
214214
215test "zig fmt: C style containers" {
216 try testError(
217 \\struct Foo {
218 \\ a: u32,
219 \\};
220 , &[_]Error{
221 .c_style_container,
222 .zig_style_container,
223 });
224 try testError(
225 \\test {
226 \\ struct Foo {
227 \\ a: u32,
228 \\ };
229 \\}
230 , &[_]Error{
231 .c_style_container,
232 .zig_style_container,
233 });
234}
235
215236test "zig fmt: decl between fields" {
216237 try testError(
217238 \\const S = struct {
src/Module.zig+15-11
......@@ -3324,17 +3324,21 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
33243324 .parent_decl_node = 0,
33253325 .lazy = .{ .byte_abs = byte_abs },
33263326 }, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])});
3327 } else if (parse_err.tag == .decl_between_fields) {
3328 try mod.errNoteNonLazy(.{
3329 .file_scope = file,
3330 .parent_decl_node = 0,
3331 .lazy = .{ .byte_abs = token_starts[file.tree.errors[1].token] },
3332 }, err_msg, "field before declarations here", .{});
3333 try mod.errNoteNonLazy(.{
3334 .file_scope = file,
3335 .parent_decl_node = 0,
3336 .lazy = .{ .byte_abs = token_starts[file.tree.errors[2].token] },
3337 }, err_msg, "field after declarations here", .{});
3327 }
3328
3329 for (file.tree.errors[1..]) |note| {
3330 if (!note.is_note) break;
3331
3332 try file.tree.renderError(note, msg.writer());
3333 err_msg.notes = try mod.gpa.realloc(err_msg.notes, err_msg.notes.len + 1);
3334 err_msg.notes[err_msg.notes.len - 1] = .{
3335 .src_loc = .{
3336 .file_scope = file,
3337 .parent_decl_node = 0,
3338 .lazy = .{ .byte_abs = token_starts[note.token] },
3339 },
3340 .msg = msg.toOwnedSlice(),
3341 };
33383342 }
33393343
33403344 {
src/main.zig+17-22
......@@ -4367,7 +4367,7 @@ fn printErrsMsgToStdErr(
43674367 defer text_buf.deinit();
43684368 const writer = text_buf.writer();
43694369 try tree.renderError(parse_error, writer);
4370 const text = text_buf.items;
4370 const text = try arena.dupe(u8, text_buf.items);
43714371
43724372 var notes_buffer: [2]Compilation.AllErrors.Message = undefined;
43734373 var notes_len: usize = 0;
......@@ -4388,31 +4388,26 @@ fn printErrsMsgToStdErr(
43884388 },
43894389 };
43904390 notes_len += 1;
4391 } else if (parse_error.tag == .decl_between_fields) {
4392 const prev_loc = tree.tokenLocation(0, parse_errors[i + 1].token);
4393 notes_buffer[0] = .{
4394 .src = .{
4395 .src_path = path,
4396 .msg = "field before declarations here",
4397 .byte_offset = @intCast(u32, prev_loc.line_start),
4398 .line = @intCast(u32, prev_loc.line),
4399 .column = @intCast(u32, prev_loc.column),
4400 .source_line = tree.source[prev_loc.line_start..prev_loc.line_end],
4401 },
4402 };
4403 const next_loc = tree.tokenLocation(0, parse_errors[i + 2].token);
4404 notes_buffer[1] = .{
4391 }
4392
4393 for (parse_errors[i + 1 ..]) |note| {
4394 if (!note.is_note) break;
4395
4396 text_buf.items.len = 0;
4397 try tree.renderError(note, writer);
4398 const note_loc = tree.tokenLocation(0, note.token);
4399 notes_buffer[notes_len] = .{
44054400 .src = .{
44064401 .src_path = path,
4407 .msg = "field after declarations here",
4408 .byte_offset = @intCast(u32, next_loc.line_start),
4409 .line = @intCast(u32, next_loc.line),
4410 .column = @intCast(u32, next_loc.column),
4411 .source_line = tree.source[next_loc.line_start..next_loc.line_end],
4402 .msg = try arena.dupe(u8, text_buf.items),
4403 .byte_offset = @intCast(u32, note_loc.line_start),
4404 .line = @intCast(u32, note_loc.line),
4405 .column = @intCast(u32, note_loc.column),
4406 .source_line = tree.source[note_loc.line_start..note_loc.line_end],
44124407 },
44134408 };
4414 notes_len = 2;
4415 i += 2;
4409 i += 1;
4410 notes_len += 1;
44164411 }
44174412
44184413 const extra_offset = tree.errorOffset(parse_error);