Skip to content

Commit 7090f04

Browse files
authored
Merge pull request #12083 from Vexu/c-container-err
parser: add helpful error for C style container declarations
2 parents 8033767 + 2a3f376 commit 7090f04

File tree

5 files changed

+121
-37
lines changed

5 files changed

+121
-37
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: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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

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: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -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

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

0 commit comments

Comments
 (0)