Commit 2ed1ed9b32

Andrew Kelley <andrew@ziglang.org>
2020-12-05 01:21:55
stage2: introduce Module.failed_root_source_file
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.
1 parent 1c5606a
Changed files (4)
src/Compilation.zig
@@ -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});
+                },
+            }
         }
     };
 
@@ -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 {
@@ -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);
@@ -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.
@@ -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", .{}),
+            },
         });
     }
 
@@ -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;
     }
src/Module.zig
@@ -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,
src/test.zig
@@ -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 {
@@ -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, ":");
@@ -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;
@@ -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).?;
@@ -618,12 +672,34 @@ 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);
                         }
                     }
@@ -631,7 +707,10 @@ pub const TestContext = struct {
                     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);
                         }
                     }
test/stage2/test.zig
@@ -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(
@@ -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(