Skip to content

Commit 5d22204

Browse files
committed
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 commit 5d22204

File tree

5 files changed

+114
-3
lines changed

5 files changed

+114
-3
lines changed

lib/std/zig/Ast.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,27 @@ test "zig fmt: top-level fields" {
212212
);
213213
}
214214

215+
test "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+
215236
test "zig fmt: decl between fields" {
216237
try testError(
217238
\\const S = struct {

src/Module.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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);

0 commit comments

Comments
 (0)