Skip to content

Commit

Permalink
stage2: introduce Module.failed_root_source_file
Browse files Browse the repository at this point in the history
Use case:

zig build-exe non_existent_file.zig

Previous behavior:

error.FileNotFound, followed by an error return trace

Behavior after this commit:

error: unable to read non_existent_file.zig: FileNotFound
(end of stderr, exit code 1)

This turns AllErrors.Message into a tagged union which now has the
capability to represent both "plain" errors as well as source-based
errors (with file, line, column, byte offset). The "no entry point found"
error has moved to be a plain error message.
  • Loading branch information
andrewrk committed Dec 5, 2020
1 parent 1c5606a commit 2ed1ed9
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 53 deletions.
107 changes: 71 additions & 36 deletions src/Compilation.zig
Expand Up @@ -226,20 +226,32 @@ pub const AllErrors = struct {
arena: std.heap.ArenaAllocator.State,
list: []const Message,

pub const Message = struct {
src_path: []const u8,
line: usize,
column: usize,
byte_offset: usize,
msg: []const u8,
pub const Message = union(enum) {
src: struct {
src_path: []const u8,
line: usize,
column: usize,
byte_offset: usize,
msg: []const u8,
},
plain: struct {
msg: []const u8,
},

pub fn renderToStdErr(self: Message) void {
std.debug.print("{}:{}:{}: error: {}\n", .{
self.src_path,
self.line + 1,
self.column + 1,
self.msg,
});
switch (self) {
.src => |src| {
std.debug.print("{s}:{d}:{d}: error: {s}\n", .{
src.src_path,
src.line + 1,
src.column + 1,
src.msg,
});
},
.plain => |plain| {
std.debug.print("error: {s}\n", .{plain.msg});
},
}
}
};

Expand All @@ -256,13 +268,23 @@ pub const AllErrors = struct {
) !void {
const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset);
try errors.append(.{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
.byte_offset = simple_err_msg.byte_offset,
.line = loc.line,
.column = loc.column,
.src = .{
.src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
.byte_offset = simple_err_msg.byte_offset,
.line = loc.line,
.column = loc.column,
},
});
}

fn addPlain(
arena: *std.heap.ArenaAllocator,
errors: *std.ArrayList(Message),
msg: []const u8,
) !void {
try errors.append(.{ .plain = .{ .msg = msg } });
}
};

pub const Directory = struct {
Expand Down Expand Up @@ -1169,11 +1191,15 @@ pub fn update(self: *Compilation) !void {
// to force a refresh we unload now.
if (module.root_scope.cast(Module.Scope.File)) |zig_file| {
zig_file.unload(module.gpa);
module.failed_root_src_file = null;
module.analyzeContainer(&zig_file.root_container) catch |err| switch (err) {
error.AnalysisFail => {
assert(self.totalErrorCount() != 0);
},
else => |e| return e,
error.OutOfMemory => return error.OutOfMemory,
else => |e| {
module.failed_root_src_file = e;
},
};
} else if (module.root_scope.cast(Module.Scope.ZIRModule)) |zir_module| {
zir_module.unload(module.gpa);
Expand Down Expand Up @@ -1251,7 +1277,8 @@ pub fn totalErrorCount(self: *Compilation) usize {
if (self.bin_file.options.module) |module| {
total += module.failed_decls.items().len +
module.failed_exports.items().len +
module.failed_files.items().len;
module.failed_files.items().len +
@boolToInt(module.failed_root_src_file != null);
}

// The "no entry point found" error only counts if there are no other errors.
Expand Down Expand Up @@ -1293,21 +1320,22 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
const source = try decl.scope.getSource(module);
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
}
if (module.failed_root_src_file) |err| {
const file_path = try module.root_pkg.root_src_directory.join(&arena.allocator, &[_][]const u8{
module.root_pkg.root_src_path,
});
const msg = try std.fmt.allocPrint(&arena.allocator, "unable to read {s}: {s}", .{
file_path, @errorName(err),
});
try AllErrors.addPlain(&arena, &errors, msg);
}
}

if (errors.items.len == 0 and self.link_error_flags.no_entry_point_found) {
const global_err_src_path = blk: {
if (self.bin_file.options.module) |module| break :blk module.root_pkg.root_src_path;
if (self.c_source_files.len != 0) break :blk self.c_source_files[0].src_path;
if (self.bin_file.options.objects.len != 0) break :blk self.bin_file.options.objects[0];
break :blk "(no file)";
};
try errors.append(.{
.src_path = global_err_src_path,
.line = 0,
.column = 0,
.byte_offset = 0,
.msg = try std.fmt.allocPrint(&arena.allocator, "no entry point found", .{}),
.plain = .{
.msg = try std.fmt.allocPrint(&arena.allocator, "no entry point found", .{}),
},
});
}

Expand Down Expand Up @@ -2644,12 +2672,19 @@ pub fn updateSubCompilation(sub_compilation: *Compilation) !void {

if (errors.list.len != 0) {
for (errors.list) |full_err_msg| {
log.err("{}:{}:{}: {}\n", .{
full_err_msg.src_path,
full_err_msg.line + 1,
full_err_msg.column + 1,
full_err_msg.msg,
});
switch (full_err_msg) {
.src => |src| {
log.err("{s}:{d}:{d}: {s}\n", .{
src.src_path,
src.line + 1,
src.column + 1,
src.msg,
});
},
.plain => |plain| {
log.err("{s}", .{plain.msg});
},
}
}
return error.BuildingLibCObjectFailed;
}
Expand Down
3 changes: 3 additions & 0 deletions src/Module.zig
Expand Up @@ -78,6 +78,9 @@ import_table: std.StringArrayHashMapUnmanaged(*Scope.File) = .{},
/// previous analysis.
generation: u32 = 0,

/// When populated it means there was an error opening/reading the root source file.
failed_root_src_file: ?anyerror = null,

stage1_flags: packed struct {
have_winmain: bool = false,
have_wwinmain: bool = false,
Expand Down
109 changes: 94 additions & 15 deletions src/test.zig
Expand Up @@ -22,10 +22,52 @@ test "self-hosted" {
try ctx.run();
}

const ErrorMsg = struct {
msg: []const u8,
line: u32,
column: u32,
const ErrorMsg = union(enum) {
src: struct {
msg: []const u8,
line: u32,
column: u32,
},
plain: struct {
msg: []const u8,
},

fn init(other: Compilation.AllErrors.Message) ErrorMsg {
switch (other) {
.src => |src| return .{
.src = .{
.msg = src.msg,
.line = @intCast(u32, src.line),
.column = @intCast(u32, src.column),
},
},
.plain => |plain| return .{
.plain = .{
.msg = plain.msg,
},
},
}
}

pub fn format(
self: ErrorMsg,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.src => |src| {
return writer.print(":{d}:{d}: error: {s}", .{
src.line + 1,
src.column + 1,
src.msg,
});
},
.plain => |plain| {
return writer.print("error: {s}", .{plain.msg});
},
}
}
};

pub const TestContext = struct {
Expand Down Expand Up @@ -112,7 +154,8 @@ pub const TestContext = struct {
var array = self.updates.allocator.alloc(ErrorMsg, errors.len) catch unreachable;
for (errors) |e, i| {
if (e[0] != ':') {
@panic("Invalid test: error must be specified as follows:\n:line:column: error: message\n=========\n");
array[i] = .{ .plain = .{ .msg = e } };
continue;
}
var cur = e[1..];
var line_index = std.mem.indexOf(u8, cur, ":");
Expand All @@ -137,9 +180,11 @@ pub const TestContext = struct {
}

array[i] = .{
.msg = msg,
.line = line - 1,
.column = column - 1,
.src = .{
.msg = msg,
.line = line - 1,
.column = column - 1,
},
};
}
self.updates.append(.{ .src = src, .case = .{ .Error = array } }) catch unreachable;
Expand Down Expand Up @@ -544,8 +589,17 @@ pub const TestContext = struct {
defer all_errors.deinit(allocator);
if (all_errors.list.len != 0) {
std.debug.print("\nErrors occurred updating the compilation:\n================\n", .{});
for (all_errors.list) |err| {
std.debug.print(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
for (all_errors.list) |err_msg| {
switch (err_msg) {
.src => |src| {
std.debug.print(":{d}:{d}: error: {s}\n================\n", .{
src.line + 1, src.column + 1, src.msg,
});
},
.plain => |plain| {
std.debug.print("error: {s}\n================\n", .{plain.msg});
},
}
}
if (case.cbe) {
const C = comp.bin_file.cast(link.File.C).?;
Expand Down Expand Up @@ -618,20 +672,45 @@ pub const TestContext = struct {
defer all_errors.deinit(allocator);
for (all_errors.list) |a| {
for (e) |ex, i| {
if (a.line == ex.line and a.column == ex.column and std.mem.eql(u8, ex.msg, a.msg)) {
handled_errors[i] = true;
break;
const a_tag: @TagType(@TypeOf(a)) = a;
const ex_tag: @TagType(@TypeOf(ex)) = ex;
switch (a) {
.src => |src| {
if (ex_tag != .src) continue;

if (src.line == ex.src.line and
src.column == ex.src.column and
std.mem.eql(u8, ex.src.msg, src.msg))
{
handled_errors[i] = true;
break;
}
},
.plain => |plain| {
if (ex_tag != .plain) continue;

if (std.mem.eql(u8, ex.plain.msg, plain.msg)) {
handled_errors[i] = true;
break;
}
},
}
} else {
std.debug.print("{}\nUnexpected error:\n================\n:{}:{}: error: {}\n================\nTest failed.\n", .{ case.name, a.line + 1, a.column + 1, a.msg });
std.debug.print(
"{s}\nUnexpected error:\n================\n{}\n================\nTest failed.\n",
.{ case.name, ErrorMsg.init(a) },
);
std.process.exit(1);
}
}

for (handled_errors) |h, i| {
if (!h) {
const er = e[i];
std.debug.print("{}\nDid not receive error:\n================\n{}:{}: {}\n================\nTest failed.\n", .{ case.name, er.line, er.column, er.msg });
std.debug.print(
"{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n",
.{ case.name, er },
);
std.process.exit(1);
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/stage2/test.zig
Expand Up @@ -36,7 +36,7 @@ pub fn addCases(ctx: *TestContext) !void {
{
var case = ctx.exe("hello world with updates", linux_x64);

case.addError("", &[_][]const u8{":1:1: error: no entry point found"});
case.addError("", &[_][]const u8{"no entry point found"});

// Incorrect return type
case.addError(
Expand Down Expand Up @@ -147,7 +147,7 @@ pub fn addCases(ctx: *TestContext) !void {

{
var case = ctx.exe("hello world with updates", macosx_x64);
case.addError("", &[_][]const u8{":1:1: error: no entry point found"});
case.addError("", &[_][]const u8{"no entry point found"});

// Incorrect return type
case.addError(
Expand Down

0 comments on commit 2ed1ed9

Please sign in to comment.