authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-11 23:51:57+03:00
committergravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2022-07-12 12:50:59+03:00
log5d22204d2dbf686d8b827eca15253914ef9543ab
tree9c5e331329593fc8bc896261416513c0d5acddbb
parent7d2e14267985df0226a5deee96d0c17c94bf6eb2

parser: add helpful error for C style container declarations

```zig // a.zig struct Foo { a: u32, }; ``` before: ``` a.zig:1:1: error: expected test, comptime, var decl, or container field, found 'struct' struct Foo { ^ ``` after: ``` a.zig:1:8: error: 'struct Foo' is invalid struct Foo { ^ a.zig:1:8: note: to declare a container do 'const Foo = struct' struct Foo { ^ ```

5 files changed, 114 insertions(+), 3 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+54-3
......@@ -440,9 +440,15 @@ const Parser = struct {
440440 break;
441441 },
442442 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();
443 const c_container = p.parseCStyleContainer() catch |err| switch (err) {
444 error.OutOfMemory => return error.OutOfMemory,
445 error.ParseError => false,
446 };
447 if (!c_container) {
448 try p.warn(.expected_container_members);
449 // This was likely not supposed to end yet; try to find the next declaration.
450 p.findNextContainerMember();
451 }
446452 },
447453 }
448454 }
......@@ -978,6 +984,20 @@ const Parser = struct {
978984 }),
979985 .keyword_switch => return p.expectSwitchExpr(),
980986 .keyword_if => return p.expectIfStatement(),
987 .keyword_enum, .keyword_struct, .keyword_union => {
988 const identifier = p.tok_i + 2;
989 if (try p.parseCStyleContainer()) {
990 // Return something so that `expectStatement` is happy.
991 return p.addNode(.{
992 .tag = .identifier,
993 .main_token = identifier,
994 .data = .{
995 .lhs = undefined,
996 .rhs = undefined,
997 },
998 });
999 }
1000 },
9811001 else => {},
9821002 }
9831003
......@@ -3466,6 +3486,37 @@ const Parser = struct {
34663486 }
34673487 }
34683488
3489 /// Give a helpful error message for those transitioning from
3490 /// C's 'struct Foo {};' to Zig's 'const Foo = struct {};'.
3491 fn parseCStyleContainer(p: *Parser) Error!bool {
3492 const main_token = p.tok_i;
3493 switch (p.token_tags[p.tok_i]) {
3494 .keyword_enum, .keyword_union, .keyword_struct => {},
3495 else => return false,
3496 }
3497 const identifier = p.tok_i + 1;
3498 if (p.token_tags[identifier] != .identifier) return false;
3499 p.tok_i += 2;
3500
3501 try p.warnMsg(.{
3502 .tag = .c_style_container,
3503 .token = identifier,
3504 .extra = .{ .expected_tag = p.token_tags[main_token] },
3505 });
3506 try p.warnMsg(.{
3507 .tag = .zig_style_container,
3508 .is_note = true,
3509 .token = identifier,
3510 .extra = .{ .expected_tag = p.token_tags[main_token] },
3511 });
3512
3513 _ = try p.expectToken(.l_brace);
3514 _ = try p.parseContainerMembers();
3515 _ = try p.expectToken(.r_brace);
3516 try p.expectSemicolon(.expected_semi_after_decl, true);
3517 return true;
3518 }
3519
34693520 /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
34703521 /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
34713522 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+9
......@@ -3335,6 +3335,15 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
33353335 .parent_decl_node = 0,
33363336 .lazy = .{ .byte_abs = token_starts[file.tree.errors[2].token] },
33373337 }, err_msg, "field after declarations here", .{});
3338 } else if (parse_err.tag == .c_style_container) {
3339 const note = file.tree.errors[1];
3340 try mod.errNoteNonLazy(.{
3341 .file_scope = file,
3342 .parent_decl_node = 0,
3343 .lazy = .{ .byte_abs = token_starts[note.token] },
3344 }, err_msg, "to declare a container do 'const {s} = {s}'", .{
3345 file.tree.tokenSlice(note.token), note.extra.expected_tag.symbol(),
3346 });
33383347 }
33393348
33403349 {
src/main.zig+18
......@@ -4413,6 +4413,24 @@ fn printErrsMsgToStdErr(
44134413 };
44144414 notes_len = 2;
44154415 i += 2;
4416 } else if (parse_error.tag == .c_style_container) {
4417 const note = tree.errors[i + 1];
4418
4419 const prev_loc = tree.tokenLocation(0, parse_errors[i + 1].token);
4420 notes_buffer[0] = .{
4421 .src = .{
4422 .src_path = path,
4423 .msg = try std.fmt.allocPrint(arena, "to declare a container do 'const {s} = {s}'", .{
4424 tree.tokenSlice(note.token), note.extra.expected_tag.symbol(),
4425 }),
4426 .byte_offset = @intCast(u32, prev_loc.line_start),
4427 .line = @intCast(u32, prev_loc.line),
4428 .column = @intCast(u32, prev_loc.column),
4429 .source_line = tree.source[prev_loc.line_start..prev_loc.line_end],
4430 },
4431 };
4432 notes_len = 1;
4433 i += 1;
44164434 }
44174435
44184436 const extra_offset = tree.errorOffset(parse_error);