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 {...@@ -334,6 +334,16 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
334 .invalid_ampersand_ampersand => {334 .invalid_ampersand_ampersand => {
335 return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");335 return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");
336 },336 },
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 },
337 .previous_field => {347 .previous_field => {
338 return stream.writeAll("field before declarations here");348 return stream.writeAll("field before declarations here");
339 },349 },
...@@ -2541,7 +2551,9 @@ pub const Error = struct {...@@ -2541,7 +2551,9 @@ pub const Error = struct {
2541 expected_initializer,2551 expected_initializer,
2542 mismatched_binary_op_whitespace,2552 mismatched_binary_op_whitespace,
2543 invalid_ampersand_ampersand,2553 invalid_ampersand_ampersand,
2554 c_style_container,
25442555
2556 zig_style_container,
2545 previous_field,2557 previous_field,
2546 next_field,2558 next_field,
25472559
lib/std/zig/parse.zig+56-4
...@@ -178,7 +178,6 @@ const Parser = struct {...@@ -178,7 +178,6 @@ const Parser = struct {
178 .expected_block_or_assignment,178 .expected_block_or_assignment,
179 .expected_block_or_expr,179 .expected_block_or_expr,
180 .expected_block_or_field,180 .expected_block_or_field,
181 .expected_container_members,
182 .expected_expr,181 .expected_expr,
183 .expected_expr_or_assignment,182 .expected_expr_or_assignment,
184 .expected_fn,183 .expected_fn,
...@@ -401,10 +400,12 @@ const Parser = struct {...@@ -401,10 +400,12 @@ const Parser = struct {
401 });400 });
402 try p.warnMsg(.{401 try p.warnMsg(.{
403 .tag = .previous_field,402 .tag = .previous_field,
403 .is_note = true,
404 .token = last_field,404 .token = last_field,
405 });405 });
406 try p.warnMsg(.{406 try p.warnMsg(.{
407 .tag = .next_field,407 .tag = .next_field,
408 .is_note = true,
408 .token = identifier,409 .token = identifier,
409 });410 });
410 // Continue parsing; error will be reported later.411 // Continue parsing; error will be reported later.
...@@ -440,9 +441,15 @@ const Parser = struct {...@@ -440,9 +441,15 @@ const Parser = struct {
440 break;441 break;
441 },442 },
442 else => {443 else => {
443 try p.warn(.expected_container_members);444 const c_container = p.parseCStyleContainer() catch |err| switch (err) {
444 // This was likely not supposed to end yet; try to find the next declaration.445 error.OutOfMemory => return error.OutOfMemory,
445 p.findNextContainerMember();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 }
446 },453 },
447 }454 }
448 }455 }
...@@ -978,6 +985,20 @@ const Parser = struct {...@@ -978,6 +985,20 @@ const Parser = struct {
978 }),985 }),
979 .keyword_switch => return p.expectSwitchExpr(),986 .keyword_switch => return p.expectSwitchExpr(),
980 .keyword_if => return p.expectIfStatement(),987 .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 },
981 else => {},1002 else => {},
982 }1003 }
9831004
...@@ -3466,6 +3487,37 @@ const Parser = struct {...@@ -3466,6 +3487,37 @@ const Parser = struct {
3466 }3487 }
3467 }3488 }
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
3469 /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.3521 /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
3470 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN3522 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
3471 fn parseByteAlign(p: *Parser) !Node.Index {3523 fn parseByteAlign(p: *Parser) !Node.Index {
lib/std/zig/parser_test.zig+21
...@@ -212,6 +212,27 @@ test "zig fmt: top-level fields" {...@@ -212,6 +212,27 @@ test "zig fmt: top-level fields" {
212 );212 );
213}213}
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
215test "zig fmt: decl between fields" {236test "zig fmt: decl between fields" {
216 try testError(237 try testError(
217 \\const S = struct {238 \\const S = struct {
src/Module.zig+15-11
...@@ -3324,17 +3324,21 @@ pub fn astGenFile(mod: *Module, file: *File) !void {...@@ -3324,17 +3324,21 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
3324 .parent_decl_node = 0,3324 .parent_decl_node = 0,
3325 .lazy = .{ .byte_abs = byte_abs },3325 .lazy = .{ .byte_abs = byte_abs },
3326 }, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])});3326 }, err_msg, "invalid byte: '{'}'", .{std.zig.fmtEscapes(source[byte_abs..][0..1])});
3327 } else if (parse_err.tag == .decl_between_fields) {3327 }
3328 try mod.errNoteNonLazy(.{3328
3329 .file_scope = file,3329 for (file.tree.errors[1..]) |note| {
3330 .parent_decl_node = 0,3330 if (!note.is_note) break;
3331 .lazy = .{ .byte_abs = token_starts[file.tree.errors[1].token] },3331
3332 }, err_msg, "field before declarations here", .{});3332 try file.tree.renderError(note, msg.writer());
3333 try mod.errNoteNonLazy(.{3333 err_msg.notes = try mod.gpa.realloc(err_msg.notes, err_msg.notes.len + 1);
3334 .file_scope = file,3334 err_msg.notes[err_msg.notes.len - 1] = .{
3335 .parent_decl_node = 0,3335 .src_loc = .{
3336 .lazy = .{ .byte_abs = token_starts[file.tree.errors[2].token] },3336 .file_scope = file,
3337 }, err_msg, "field after declarations here", .{});3337 .parent_decl_node = 0,
3338 .lazy = .{ .byte_abs = token_starts[note.token] },
3339 },
3340 .msg = msg.toOwnedSlice(),
3341 };
3338 }3342 }
33393343
3340 {3344 {
src/main.zig+17-22
...@@ -4367,7 +4367,7 @@ fn printErrsMsgToStdErr(...@@ -4367,7 +4367,7 @@ fn printErrsMsgToStdErr(
4367 defer text_buf.deinit();4367 defer text_buf.deinit();
4368 const writer = text_buf.writer();4368 const writer = text_buf.writer();
4369 try tree.renderError(parse_error, writer);4369 try tree.renderError(parse_error, writer);
4370 const text = text_buf.items;4370 const text = try arena.dupe(u8, text_buf.items);
43714371
4372 var notes_buffer: [2]Compilation.AllErrors.Message = undefined;4372 var notes_buffer: [2]Compilation.AllErrors.Message = undefined;
4373 var notes_len: usize = 0;4373 var notes_len: usize = 0;
...@@ -4388,31 +4388,26 @@ fn printErrsMsgToStdErr(...@@ -4388,31 +4388,26 @@ fn printErrsMsgToStdErr(
4388 },4388 },
4389 };4389 };
4390 notes_len += 1;4390 notes_len += 1;
4391 } else if (parse_error.tag == .decl_between_fields) {4391 }
4392 const prev_loc = tree.tokenLocation(0, parse_errors[i + 1].token);4392
4393 notes_buffer[0] = .{4393 for (parse_errors[i + 1 ..]) |note| {
4394 .src = .{4394 if (!note.is_note) break;
4395 .src_path = path,4395
4396 .msg = "field before declarations here",4396 text_buf.items.len = 0;
4397 .byte_offset = @intCast(u32, prev_loc.line_start),4397 try tree.renderError(note, writer);
4398 .line = @intCast(u32, prev_loc.line),4398 const note_loc = tree.tokenLocation(0, note.token);
4399 .column = @intCast(u32, prev_loc.column),4399 notes_buffer[notes_len] = .{
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] = .{
4405 .src = .{4400 .src = .{
4406 .src_path = path,4401 .src_path = path,
4407 .msg = "field after declarations here",4402 .msg = try arena.dupe(u8, text_buf.items),
4408 .byte_offset = @intCast(u32, next_loc.line_start),4403 .byte_offset = @intCast(u32, note_loc.line_start),
4409 .line = @intCast(u32, next_loc.line),4404 .line = @intCast(u32, note_loc.line),
4410 .column = @intCast(u32, next_loc.column),4405 .column = @intCast(u32, note_loc.column),
4411 .source_line = tree.source[next_loc.line_start..next_loc.line_end],4406 .source_line = tree.source[note_loc.line_start..note_loc.line_end],
4412 },4407 },
4413 };4408 };
4414 notes_len = 2;4409 i += 1;
4415 i += 2;4410 notes_len += 1;
4416 }4411 }
44174412
4418 const extra_offset = tree.errorOffset(parse_error);4413 const extra_offset = tree.errorOffset(parse_error);