Commit 5d22204d2d

Veikka Tuominen <git@vexu.eu>
2022-07-11 22:51:57
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 { ^ ```
1 parent 7d2e142
Changed files (5)
lib/std/zig/Ast.zig
@@ -334,6 +334,16 @@ pub fn renderError(tree: Ast, parse_error: Error, stream: anytype) !void {
         .invalid_ampersand_ampersand => {
             return stream.writeAll("ambiguous use of '&&'; use 'and' for logical AND, or change whitespace to ' & &' for bitwise AND");
         },
+        .c_style_container => {
+            return stream.print("'{s} {s}' is invalid", .{
+                parse_error.extra.expected_tag.symbol(), tree.tokenSlice(parse_error.token),
+            });
+        },
+        .zig_style_container => {
+            return stream.print("to declare a container do 'const {s} = {s}'", .{
+                tree.tokenSlice(parse_error.token), parse_error.extra.expected_tag.symbol(),
+            });
+        },
         .previous_field => {
             return stream.writeAll("field before declarations here");
         },
@@ -2541,7 +2551,9 @@ pub const Error = struct {
         expected_initializer,
         mismatched_binary_op_whitespace,
         invalid_ampersand_ampersand,
+        c_style_container,
 
+        zig_style_container,
         previous_field,
         next_field,
 
lib/std/zig/parse.zig
@@ -440,9 +440,15 @@ const Parser = struct {
                     break;
                 },
                 else => {
-                    try p.warn(.expected_container_members);
-                    // This was likely not supposed to end yet; try to find the next declaration.
-                    p.findNextContainerMember();
+                    const c_container = p.parseCStyleContainer() catch |err| switch (err) {
+                        error.OutOfMemory => return error.OutOfMemory,
+                        error.ParseError => false,
+                    };
+                    if (!c_container) {
+                        try p.warn(.expected_container_members);
+                        // This was likely not supposed to end yet; try to find the next declaration.
+                        p.findNextContainerMember();
+                    }
                 },
             }
         }
@@ -978,6 +984,20 @@ const Parser = struct {
             }),
             .keyword_switch => return p.expectSwitchExpr(),
             .keyword_if => return p.expectIfStatement(),
+            .keyword_enum, .keyword_struct, .keyword_union => {
+                const identifier = p.tok_i + 2;
+                if (try p.parseCStyleContainer()) {
+                    // Return something so that `expectStatement` is happy.
+                    return p.addNode(.{
+                        .tag = .identifier,
+                        .main_token = identifier,
+                        .data = .{
+                            .lhs = undefined,
+                            .rhs = undefined,
+                        },
+                    });
+                }
+            },
             else => {},
         }
 
@@ -3466,6 +3486,37 @@ const Parser = struct {
         }
     }
 
+    /// Give a helpful error message for those transitioning from
+    /// C's 'struct Foo {};' to Zig's 'const Foo = struct {};'.
+    fn parseCStyleContainer(p: *Parser) Error!bool {
+        const main_token = p.tok_i;
+        switch (p.token_tags[p.tok_i]) {
+            .keyword_enum, .keyword_union, .keyword_struct => {},
+            else => return false,
+        }
+        const identifier = p.tok_i + 1;
+        if (p.token_tags[identifier] != .identifier) return false;
+        p.tok_i += 2;
+
+        try p.warnMsg(.{
+            .tag = .c_style_container,
+            .token = identifier,
+            .extra = .{ .expected_tag = p.token_tags[main_token] },
+        });
+        try p.warnMsg(.{
+            .tag = .zig_style_container,
+            .is_note = true,
+            .token = identifier,
+            .extra = .{ .expected_tag = p.token_tags[main_token] },
+        });
+
+        _ = try p.expectToken(.l_brace);
+        _ = try p.parseContainerMembers();
+        _ = try p.expectToken(.r_brace);
+        try p.expectSemicolon(.expected_semi_after_decl, true);
+        return true;
+    }
+
     /// Holds temporary data until we are ready to construct the full ContainerDecl AST node.
     /// ByteAlign <- KEYWORD_align LPAREN Expr RPAREN
     fn parseByteAlign(p: *Parser) !Node.Index {
lib/std/zig/parser_test.zig
@@ -212,6 +212,27 @@ test "zig fmt: top-level fields" {
     );
 }
 
+test "zig fmt: C style containers" {
+    try testError(
+        \\struct Foo {
+        \\    a: u32,
+        \\};
+    , &[_]Error{
+        .c_style_container,
+        .zig_style_container,
+    });
+    try testError(
+        \\test {
+        \\    struct Foo {
+        \\        a: u32,
+        \\    };
+        \\}
+    , &[_]Error{
+        .c_style_container,
+        .zig_style_container,
+    });
+}
+
 test "zig fmt: decl between fields" {
     try testError(
         \\const S = struct {
src/main.zig
@@ -4413,6 +4413,24 @@ fn printErrsMsgToStdErr(
             };
             notes_len = 2;
             i += 2;
+        } else if (parse_error.tag == .c_style_container) {
+            const note = tree.errors[i + 1];
+
+            const prev_loc = tree.tokenLocation(0, parse_errors[i + 1].token);
+            notes_buffer[0] = .{
+                .src = .{
+                    .src_path = path,
+                    .msg = try std.fmt.allocPrint(arena, "to declare a container do 'const {s} = {s}'", .{
+                        tree.tokenSlice(note.token), note.extra.expected_tag.symbol(),
+                    }),
+                    .byte_offset = @intCast(u32, prev_loc.line_start),
+                    .line = @intCast(u32, prev_loc.line),
+                    .column = @intCast(u32, prev_loc.column),
+                    .source_line = tree.source[prev_loc.line_start..prev_loc.line_end],
+                },
+            };
+            notes_len = 1;
+            i += 1;
         }
 
         const extra_offset = tree.errorOffset(parse_error);
src/Module.zig
@@ -3335,6 +3335,15 @@ pub fn astGenFile(mod: *Module, file: *File) !void {
                 .parent_decl_node = 0,
                 .lazy = .{ .byte_abs = token_starts[file.tree.errors[2].token] },
             }, err_msg, "field after declarations here", .{});
+        } else if (parse_err.tag == .c_style_container) {
+            const note = file.tree.errors[1];
+            try mod.errNoteNonLazy(.{
+                .file_scope = file,
+                .parent_decl_node = 0,
+                .lazy = .{ .byte_abs = token_starts[note.token] },
+            }, err_msg, "to declare a container do 'const {s} = {s}'", .{
+                file.tree.tokenSlice(note.token), note.extra.expected_tag.symbol(),
+            });
         }
 
         {