mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 05:44:20 +00:00
This takes the code that was previously in src/Compilation.zig to turn resinator diagnostics into Zig error bundles and puts it in resinator/main.zig, and then makes resinator emit the resulting error bundles via std.zig.Server (which is used by the build runner, etc). Also adds support for turning Aro diagnostics into ErrorBundles.
708 lines
27 KiB
Zig
708 lines
27 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const removeComments = @import("comments.zig").removeComments;
|
|
const parseAndRemoveLineCommands = @import("source_mapping.zig").parseAndRemoveLineCommands;
|
|
const compile = @import("compile.zig").compile;
|
|
const Diagnostics = @import("errors.zig").Diagnostics;
|
|
const cli = @import("cli.zig");
|
|
const preprocess = @import("preprocess.zig");
|
|
const renderErrorMessage = @import("utils.zig").renderErrorMessage;
|
|
const aro = @import("aro");
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer std.debug.assert(gpa.deinit() == .ok);
|
|
const allocator = gpa.allocator();
|
|
|
|
const stderr = std.io.getStdErr();
|
|
const stderr_config = std.io.tty.detectConfig(stderr);
|
|
|
|
const args = try std.process.argsAlloc(allocator);
|
|
defer std.process.argsFree(allocator, args);
|
|
|
|
if (args.len < 2) {
|
|
try renderErrorMessage(stderr.writer(), stderr_config, .err, "expected zig lib dir as first argument", .{});
|
|
std.os.exit(1);
|
|
}
|
|
const zig_lib_dir = args[1];
|
|
var cli_args = args[2..];
|
|
|
|
var zig_integration = false;
|
|
if (cli_args.len > 0 and std.mem.eql(u8, cli_args[0], "--zig-integration")) {
|
|
zig_integration = true;
|
|
cli_args = args[3..];
|
|
}
|
|
|
|
var error_handler: ErrorHandler = switch (zig_integration) {
|
|
true => .{
|
|
.server = .{
|
|
.out = std.io.getStdOut(),
|
|
.in = undefined, // won't be receiving messages
|
|
.receive_fifo = undefined, // won't be receiving messages
|
|
},
|
|
},
|
|
false => .{
|
|
.tty = stderr_config,
|
|
},
|
|
};
|
|
|
|
var options = options: {
|
|
var cli_diagnostics = cli.Diagnostics.init(allocator);
|
|
defer cli_diagnostics.deinit();
|
|
var options = cli.parse(allocator, cli_args, &cli_diagnostics) catch |err| switch (err) {
|
|
error.ParseError => {
|
|
try error_handler.emitCliDiagnostics(allocator, cli_args, &cli_diagnostics);
|
|
std.os.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
try options.maybeAppendRC(std.fs.cwd());
|
|
|
|
if (!zig_integration) {
|
|
// print any warnings/notes
|
|
cli_diagnostics.renderToStdErr(args, stderr_config);
|
|
// If there was something printed, then add an extra newline separator
|
|
// so that there is a clear separation between the cli diagnostics and whatever
|
|
// gets printed after
|
|
if (cli_diagnostics.errors.items.len > 0) {
|
|
try stderr.writeAll("\n");
|
|
}
|
|
}
|
|
break :options options;
|
|
};
|
|
defer options.deinit();
|
|
|
|
if (options.print_help_and_exit) {
|
|
try cli.writeUsage(stderr.writer(), "zig rc");
|
|
return;
|
|
}
|
|
|
|
// Don't allow verbose when integrating with Zig via stdout
|
|
options.verbose = false;
|
|
|
|
const stdout_writer = std.io.getStdOut().writer();
|
|
if (options.verbose) {
|
|
try options.dumpVerbose(stdout_writer);
|
|
try stdout_writer.writeByte('\n');
|
|
}
|
|
|
|
var dependencies_list = std.ArrayList([]const u8).init(allocator);
|
|
defer {
|
|
for (dependencies_list.items) |item| {
|
|
allocator.free(item);
|
|
}
|
|
dependencies_list.deinit();
|
|
}
|
|
const maybe_dependencies_list: ?*std.ArrayList([]const u8) = if (options.depfile_path != null) &dependencies_list else null;
|
|
|
|
const full_input = full_input: {
|
|
if (options.preprocess != .no) {
|
|
var preprocessed_buf = std.ArrayList(u8).init(allocator);
|
|
errdefer preprocessed_buf.deinit();
|
|
|
|
// We're going to throw away everything except the final preprocessed output anyway,
|
|
// so we can use a scoped arena for everything else.
|
|
var aro_arena_state = std.heap.ArenaAllocator.init(allocator);
|
|
defer aro_arena_state.deinit();
|
|
const aro_arena = aro_arena_state.allocator();
|
|
|
|
const include_paths = getIncludePaths(aro_arena, options.auto_includes, zig_lib_dir) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => |e| {
|
|
switch (e) {
|
|
error.MsvcIncludesNotFound => {
|
|
try error_handler.emitMessage(allocator, .err, "MSVC include paths could not be automatically detected", .{});
|
|
},
|
|
error.MingwIncludesNotFound => {
|
|
try error_handler.emitMessage(allocator, .err, "MinGW include paths could not be automatically detected", .{});
|
|
},
|
|
}
|
|
try error_handler.emitMessage(allocator, .note, "to disable auto includes, use the option /:auto-includes none", .{});
|
|
std.os.exit(1);
|
|
},
|
|
};
|
|
|
|
var comp = aro.Compilation.init(aro_arena);
|
|
defer comp.deinit();
|
|
|
|
var argv = std.ArrayList([]const u8).init(comp.gpa);
|
|
defer argv.deinit();
|
|
|
|
try argv.append("arocc"); // dummy command name
|
|
try preprocess.appendAroArgs(aro_arena, &argv, options, include_paths);
|
|
try argv.append(options.input_filename);
|
|
|
|
if (options.verbose) {
|
|
try stdout_writer.writeAll("Preprocessor: arocc (built-in)\n");
|
|
for (argv.items[0 .. argv.items.len - 1]) |arg| {
|
|
try stdout_writer.print("{s} ", .{arg});
|
|
}
|
|
try stdout_writer.print("{s}\n\n", .{argv.items[argv.items.len - 1]});
|
|
}
|
|
|
|
preprocess.preprocess(&comp, preprocessed_buf.writer(), argv.items, maybe_dependencies_list) catch |err| switch (err) {
|
|
error.GeneratedSourceError => {
|
|
try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug):", &comp);
|
|
std.os.exit(1);
|
|
},
|
|
// ArgError can occur if e.g. the .rc file is not found
|
|
error.ArgError, error.PreprocessError => {
|
|
try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing:", &comp);
|
|
std.os.exit(1);
|
|
},
|
|
error.StreamTooLong => {
|
|
try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{});
|
|
std.os.exit(1);
|
|
},
|
|
error.OutOfMemory => |e| return e,
|
|
};
|
|
|
|
break :full_input try preprocessed_buf.toOwnedSlice();
|
|
} else {
|
|
break :full_input std.fs.cwd().readFileAlloc(allocator, options.input_filename, std.math.maxInt(usize)) catch |err| {
|
|
try error_handler.emitMessage(allocator, .err, "unable to read input file path '{s}': {s}", .{ options.input_filename, @errorName(err) });
|
|
std.os.exit(1);
|
|
};
|
|
}
|
|
};
|
|
defer allocator.free(full_input);
|
|
|
|
if (options.preprocess == .only) {
|
|
try std.fs.cwd().writeFile(options.output_filename, full_input);
|
|
return;
|
|
}
|
|
|
|
// Note: We still want to run this when no-preprocess is set because:
|
|
// 1. We want to print accurate line numbers after removing multiline comments
|
|
// 2. We want to be able to handle an already-preprocessed input with #line commands in it
|
|
var mapping_results = try parseAndRemoveLineCommands(allocator, full_input, full_input, .{ .initial_filename = options.input_filename });
|
|
defer mapping_results.mappings.deinit(allocator);
|
|
|
|
const final_input = removeComments(mapping_results.result, mapping_results.result, &mapping_results.mappings) catch |err| switch (err) {
|
|
error.InvalidSourceMappingCollapse => {
|
|
try error_handler.emitMessage(allocator, .err, "failed during comment removal; this is a known bug", .{});
|
|
std.os.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
var output_file = std.fs.cwd().createFile(options.output_filename, .{}) catch |err| {
|
|
try error_handler.emitMessage(allocator, .err, "unable to create output file '{s}': {s}", .{ options.output_filename, @errorName(err) });
|
|
std.os.exit(1);
|
|
};
|
|
var output_file_closed = false;
|
|
defer if (!output_file_closed) output_file.close();
|
|
|
|
var diagnostics = Diagnostics.init(allocator);
|
|
defer diagnostics.deinit();
|
|
|
|
var output_buffered_stream = std.io.bufferedWriter(output_file.writer());
|
|
|
|
compile(allocator, final_input, output_buffered_stream.writer(), .{
|
|
.cwd = std.fs.cwd(),
|
|
.diagnostics = &diagnostics,
|
|
.source_mappings = &mapping_results.mappings,
|
|
.dependencies_list = maybe_dependencies_list,
|
|
.ignore_include_env_var = options.ignore_include_env_var,
|
|
.extra_include_paths = options.extra_include_paths.items,
|
|
.default_language_id = options.default_language_id,
|
|
.default_code_page = options.default_code_page orelse .windows1252,
|
|
.verbose = options.verbose,
|
|
.null_terminate_string_table_strings = options.null_terminate_string_table_strings,
|
|
.max_string_literal_codepoints = options.max_string_literal_codepoints,
|
|
.silent_duplicate_control_ids = options.silent_duplicate_control_ids,
|
|
.warn_instead_of_error_on_invalid_code_page = options.warn_instead_of_error_on_invalid_code_page,
|
|
}) catch |err| switch (err) {
|
|
error.ParseError, error.CompileError => {
|
|
try error_handler.emitDiagnostics(allocator, std.fs.cwd(), final_input, &diagnostics, mapping_results.mappings);
|
|
// Delete the output file on error
|
|
output_file.close();
|
|
output_file_closed = true;
|
|
// Failing to delete is not really a big deal, so swallow any errors
|
|
std.fs.cwd().deleteFile(options.output_filename) catch {};
|
|
std.os.exit(1);
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
try output_buffered_stream.flush();
|
|
|
|
// print any warnings/notes
|
|
if (!zig_integration) {
|
|
diagnostics.renderToStdErr(std.fs.cwd(), final_input, stderr_config, mapping_results.mappings);
|
|
}
|
|
|
|
// write the depfile
|
|
if (options.depfile_path) |depfile_path| {
|
|
var depfile = std.fs.cwd().createFile(depfile_path, .{}) catch |err| {
|
|
try error_handler.emitMessage(allocator, .err, "unable to create depfile '{s}': {s}", .{ depfile_path, @errorName(err) });
|
|
std.os.exit(1);
|
|
};
|
|
defer depfile.close();
|
|
|
|
const depfile_writer = depfile.writer();
|
|
var depfile_buffered_writer = std.io.bufferedWriter(depfile_writer);
|
|
switch (options.depfile_fmt) {
|
|
.json => {
|
|
var write_stream = std.json.writeStream(depfile_buffered_writer.writer(), .{ .whitespace = .indent_2 });
|
|
defer write_stream.deinit();
|
|
|
|
try write_stream.beginArray();
|
|
for (dependencies_list.items) |dep_path| {
|
|
try write_stream.write(dep_path);
|
|
}
|
|
try write_stream.endArray();
|
|
},
|
|
}
|
|
try depfile_buffered_writer.flush();
|
|
}
|
|
}
|
|
|
|
fn getIncludePaths(arena: std.mem.Allocator, auto_includes_option: cli.Options.AutoIncludes, zig_lib_dir: []const u8) ![]const []const u8 {
|
|
var includes = auto_includes_option;
|
|
if (builtin.target.os.tag != .windows) {
|
|
switch (includes) {
|
|
// MSVC can't be found when the host isn't Windows, so short-circuit.
|
|
.msvc => return error.MsvcIncludesNotFound,
|
|
// Skip straight to gnu since we won't be able to detect MSVC on non-Windows hosts.
|
|
.any => includes = .gnu,
|
|
.none, .gnu => {},
|
|
}
|
|
}
|
|
|
|
while (true) {
|
|
switch (includes) {
|
|
.none => return &[_][]const u8{},
|
|
.any, .msvc => {
|
|
// MSVC is only detectable on Windows targets. This unreachable is to signify
|
|
// that .any and .msvc should be dealt with on non-Windows targets before this point,
|
|
// since getting MSVC include paths uses Windows-only APIs.
|
|
if (builtin.target.os.tag != .windows) unreachable;
|
|
|
|
const target_query: std.Target.Query = .{
|
|
.os_tag = .windows,
|
|
.abi = .msvc,
|
|
};
|
|
const target = std.zig.resolveTargetQueryOrFatal(target_query);
|
|
const is_native_abi = target_query.isNativeAbi();
|
|
const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, target, is_native_abi, true, null) catch {
|
|
if (includes == .any) {
|
|
// fall back to mingw
|
|
includes = .gnu;
|
|
continue;
|
|
}
|
|
return error.MsvcIncludesNotFound;
|
|
};
|
|
if (detected_libc.libc_include_dir_list.len == 0) {
|
|
if (includes == .any) {
|
|
// fall back to mingw
|
|
includes = .gnu;
|
|
continue;
|
|
}
|
|
return error.MsvcIncludesNotFound;
|
|
}
|
|
return detected_libc.libc_include_dir_list;
|
|
},
|
|
.gnu => {
|
|
const target_query: std.Target.Query = .{
|
|
.os_tag = .windows,
|
|
.abi = .gnu,
|
|
};
|
|
const target = std.zig.resolveTargetQueryOrFatal(target_query);
|
|
const is_native_abi = target_query.isNativeAbi();
|
|
const detected_libc = std.zig.LibCDirs.detect(arena, zig_lib_dir, target, is_native_abi, true, null) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => return error.MingwIncludesNotFound,
|
|
};
|
|
return detected_libc.libc_include_dir_list;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
const ErrorBundle = std.zig.ErrorBundle;
|
|
const SourceMappings = @import("source_mapping.zig").SourceMappings;
|
|
|
|
const ErrorHandler = union(enum) {
|
|
server: std.zig.Server,
|
|
tty: std.io.tty.Config,
|
|
|
|
pub fn emitCliDiagnostics(
|
|
self: *ErrorHandler,
|
|
allocator: std.mem.Allocator,
|
|
args: []const []const u8,
|
|
diagnostics: *cli.Diagnostics,
|
|
) !void {
|
|
switch (self.*) {
|
|
.server => |*server| {
|
|
var error_bundle = try cliDiagnosticsToErrorBundle(allocator, diagnostics);
|
|
defer error_bundle.deinit(allocator);
|
|
|
|
try server.serveErrorBundle(error_bundle);
|
|
},
|
|
.tty => {
|
|
diagnostics.renderToStdErr(args, self.tty);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn emitAroDiagnostics(
|
|
self: *ErrorHandler,
|
|
allocator: std.mem.Allocator,
|
|
fail_msg: []const u8,
|
|
comp: *aro.Compilation,
|
|
) !void {
|
|
switch (self.*) {
|
|
.server => |*server| {
|
|
var error_bundle = try aroDiagnosticsToErrorBundle(allocator, fail_msg, comp);
|
|
defer error_bundle.deinit(allocator);
|
|
|
|
try server.serveErrorBundle(error_bundle);
|
|
},
|
|
.tty => {
|
|
// extra newline to separate this line from the aro errors
|
|
try renderErrorMessage(std.io.getStdErr().writer(), self.tty, .err, "{s}\n", .{fail_msg});
|
|
aro.Diagnostics.render(comp, self.tty);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn emitDiagnostics(
|
|
self: *ErrorHandler,
|
|
allocator: std.mem.Allocator,
|
|
cwd: std.fs.Dir,
|
|
source: []const u8,
|
|
diagnostics: *Diagnostics,
|
|
mappings: SourceMappings,
|
|
) !void {
|
|
switch (self.*) {
|
|
.server => |*server| {
|
|
var error_bundle = try diagnosticsToErrorBundle(allocator, source, diagnostics, mappings);
|
|
defer error_bundle.deinit(allocator);
|
|
|
|
try server.serveErrorBundle(error_bundle);
|
|
},
|
|
.tty => {
|
|
diagnostics.renderToStdErr(cwd, source, self.tty, mappings);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn emitMessage(
|
|
self: *ErrorHandler,
|
|
allocator: std.mem.Allocator,
|
|
msg_type: @import("utils.zig").ErrorMessageType,
|
|
comptime format: []const u8,
|
|
args: anytype,
|
|
) !void {
|
|
switch (self.*) {
|
|
.server => |*server| {
|
|
// only emit errors
|
|
if (msg_type != .err) return;
|
|
|
|
var error_bundle = try errorStringToErrorBundle(allocator, format, args);
|
|
defer error_bundle.deinit(allocator);
|
|
|
|
try server.serveErrorBundle(error_bundle);
|
|
},
|
|
.tty => {
|
|
try renderErrorMessage(std.io.getStdErr().writer(), self.tty, msg_type, format, args);
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
fn cliDiagnosticsToErrorBundle(
|
|
gpa: std.mem.Allocator,
|
|
diagnostics: *cli.Diagnostics,
|
|
) !ErrorBundle {
|
|
@setCold(true);
|
|
|
|
var bundle: ErrorBundle.Wip = undefined;
|
|
try bundle.init(gpa);
|
|
errdefer bundle.deinit();
|
|
|
|
try bundle.addRootErrorMessage(.{
|
|
.msg = try bundle.addString("invalid command line option(s)"),
|
|
});
|
|
|
|
var cur_err: ?ErrorBundle.ErrorMessage = null;
|
|
var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .{};
|
|
defer cur_notes.deinit(gpa);
|
|
for (diagnostics.errors.items) |err_details| {
|
|
switch (err_details.type) {
|
|
.err => {
|
|
if (cur_err) |err| {
|
|
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
|
|
}
|
|
cur_err = .{
|
|
.msg = try bundle.addString(err_details.msg.items),
|
|
};
|
|
cur_notes.clearRetainingCapacity();
|
|
},
|
|
.warning => cur_err = null,
|
|
.note => {
|
|
if (cur_err == null) continue;
|
|
cur_err.?.notes_len += 1;
|
|
try cur_notes.append(gpa, .{
|
|
.msg = try bundle.addString(err_details.msg.items),
|
|
});
|
|
},
|
|
}
|
|
}
|
|
if (cur_err) |err| {
|
|
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
|
|
}
|
|
|
|
return try bundle.toOwnedBundle("");
|
|
}
|
|
|
|
fn diagnosticsToErrorBundle(
|
|
gpa: std.mem.Allocator,
|
|
source: []const u8,
|
|
diagnostics: *Diagnostics,
|
|
mappings: SourceMappings,
|
|
) !ErrorBundle {
|
|
@setCold(true);
|
|
|
|
var bundle: ErrorBundle.Wip = undefined;
|
|
try bundle.init(gpa);
|
|
errdefer bundle.deinit();
|
|
|
|
var msg_buf: std.ArrayListUnmanaged(u8) = .{};
|
|
defer msg_buf.deinit(gpa);
|
|
var cur_err: ?ErrorBundle.ErrorMessage = null;
|
|
var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .{};
|
|
defer cur_notes.deinit(gpa);
|
|
for (diagnostics.errors.items) |err_details| {
|
|
switch (err_details.type) {
|
|
.hint => continue,
|
|
// Clear the current error so that notes don't bleed into unassociated errors
|
|
.warning => {
|
|
cur_err = null;
|
|
continue;
|
|
},
|
|
.note => if (cur_err == null) continue,
|
|
.err => {},
|
|
}
|
|
const corresponding_span = mappings.getCorrespondingSpan(err_details.token.line_number).?;
|
|
const err_line = corresponding_span.start_line;
|
|
const err_filename = mappings.files.get(corresponding_span.filename_offset);
|
|
|
|
const source_line_start = err_details.token.getLineStartForErrorDisplay(source);
|
|
// Treat tab stops as 1 column wide for error display purposes,
|
|
// and add one to get a 1-based column
|
|
const column = err_details.token.calculateColumn(source, 1, source_line_start) + 1;
|
|
|
|
msg_buf.clearRetainingCapacity();
|
|
try err_details.render(msg_buf.writer(gpa), source, diagnostics.strings.items);
|
|
|
|
const src_loc = src_loc: {
|
|
var src_loc: ErrorBundle.SourceLocation = .{
|
|
.src_path = try bundle.addString(err_filename),
|
|
.line = @intCast(err_line - 1), // 1-based -> 0-based
|
|
.column = @intCast(column - 1), // 1-based -> 0-based
|
|
.span_start = 0,
|
|
.span_main = 0,
|
|
.span_end = 0,
|
|
};
|
|
if (err_details.print_source_line) {
|
|
const source_line = err_details.token.getLineForErrorDisplay(source, source_line_start);
|
|
const visual_info = err_details.visualTokenInfo(source_line_start, source_line_start + source_line.len);
|
|
src_loc.span_start = @intCast(visual_info.point_offset - visual_info.before_len);
|
|
src_loc.span_main = @intCast(visual_info.point_offset);
|
|
src_loc.span_end = @intCast(visual_info.point_offset + 1 + visual_info.after_len);
|
|
src_loc.source_line = try bundle.addString(source_line);
|
|
}
|
|
break :src_loc try bundle.addSourceLocation(src_loc);
|
|
};
|
|
|
|
switch (err_details.type) {
|
|
.err => {
|
|
if (cur_err) |err| {
|
|
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
|
|
}
|
|
cur_err = .{
|
|
.msg = try bundle.addString(msg_buf.items),
|
|
.src_loc = src_loc,
|
|
};
|
|
cur_notes.clearRetainingCapacity();
|
|
},
|
|
.note => {
|
|
cur_err.?.notes_len += 1;
|
|
try cur_notes.append(gpa, .{
|
|
.msg = try bundle.addString(msg_buf.items),
|
|
.src_loc = src_loc,
|
|
});
|
|
},
|
|
.warning, .hint => unreachable,
|
|
}
|
|
}
|
|
if (cur_err) |err| {
|
|
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
|
|
}
|
|
|
|
return try bundle.toOwnedBundle("");
|
|
}
|
|
|
|
fn flushErrorMessageIntoBundle(wip: *ErrorBundle.Wip, msg: ErrorBundle.ErrorMessage, notes: []const ErrorBundle.ErrorMessage) !void {
|
|
try wip.addRootErrorMessage(msg);
|
|
const notes_start = try wip.reserveNotes(@intCast(notes.len));
|
|
for (notes_start.., notes) |i, note| {
|
|
wip.extra.items[i] = @intFromEnum(wip.addErrorMessageAssumeCapacity(note));
|
|
}
|
|
}
|
|
|
|
fn errorStringToErrorBundle(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) !ErrorBundle {
|
|
@setCold(true);
|
|
var bundle: ErrorBundle.Wip = undefined;
|
|
try bundle.init(allocator);
|
|
errdefer bundle.deinit();
|
|
try bundle.addRootErrorMessage(.{
|
|
.msg = try bundle.printString(format, args),
|
|
});
|
|
return try bundle.toOwnedBundle("");
|
|
}
|
|
|
|
fn aroDiagnosticsToErrorBundle(
|
|
gpa: std.mem.Allocator,
|
|
fail_msg: []const u8,
|
|
comp: *aro.Compilation,
|
|
) !ErrorBundle {
|
|
@setCold(true);
|
|
|
|
var bundle: ErrorBundle.Wip = undefined;
|
|
try bundle.init(gpa);
|
|
errdefer bundle.deinit();
|
|
|
|
try bundle.addRootErrorMessage(.{
|
|
.msg = try bundle.addString(fail_msg),
|
|
});
|
|
|
|
var msg_writer = MsgWriter.init(gpa);
|
|
defer msg_writer.deinit();
|
|
var cur_err: ?ErrorBundle.ErrorMessage = null;
|
|
var cur_notes: std.ArrayListUnmanaged(ErrorBundle.ErrorMessage) = .{};
|
|
defer cur_notes.deinit(gpa);
|
|
for (comp.diagnostics.list.items) |msg| {
|
|
switch (msg.kind) {
|
|
// Clear the current error so that notes don't bleed into unassociated errors
|
|
.off, .warning => {
|
|
cur_err = null;
|
|
continue;
|
|
},
|
|
.note => if (cur_err == null) continue,
|
|
.@"fatal error", .@"error" => {},
|
|
.default => unreachable,
|
|
}
|
|
msg_writer.resetRetainingCapacity();
|
|
aro.Diagnostics.renderMessage(comp, &msg_writer, msg);
|
|
|
|
const src_loc = src_loc: {
|
|
if (msg_writer.path) |src_path| {
|
|
var src_loc: ErrorBundle.SourceLocation = .{
|
|
.src_path = try bundle.addString(src_path),
|
|
.line = msg_writer.line - 1, // 1-based -> 0-based
|
|
.column = msg_writer.col - 1, // 1-based -> 0-based
|
|
.span_start = 0,
|
|
.span_main = 0,
|
|
.span_end = 0,
|
|
};
|
|
if (msg_writer.source_line) |source_line| {
|
|
src_loc.span_start = msg_writer.span_main;
|
|
src_loc.span_main = msg_writer.span_main;
|
|
src_loc.span_end = msg_writer.span_main;
|
|
src_loc.source_line = try bundle.addString(source_line);
|
|
}
|
|
break :src_loc try bundle.addSourceLocation(src_loc);
|
|
}
|
|
break :src_loc ErrorBundle.SourceLocationIndex.none;
|
|
};
|
|
|
|
switch (msg.kind) {
|
|
.@"fatal error", .@"error" => {
|
|
if (cur_err) |err| {
|
|
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
|
|
}
|
|
cur_err = .{
|
|
.msg = try bundle.addString(msg_writer.buf.items),
|
|
.src_loc = src_loc,
|
|
};
|
|
cur_notes.clearRetainingCapacity();
|
|
},
|
|
.note => {
|
|
cur_err.?.notes_len += 1;
|
|
try cur_notes.append(gpa, .{
|
|
.msg = try bundle.addString(msg_writer.buf.items),
|
|
.src_loc = src_loc,
|
|
});
|
|
},
|
|
.off, .warning, .default => unreachable,
|
|
}
|
|
}
|
|
if (cur_err) |err| {
|
|
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
|
|
}
|
|
|
|
return try bundle.toOwnedBundle("");
|
|
}
|
|
|
|
// Similar to aro.Diagnostics.MsgWriter but:
|
|
// - Writers to an ArrayList
|
|
// - Only prints the message itself (no location, source line, error: prefix, etc)
|
|
// - Keeps track of source path/line/col instead
|
|
const MsgWriter = struct {
|
|
buf: std.ArrayList(u8),
|
|
path: ?[]const u8 = null,
|
|
// 1-indexed
|
|
line: u32 = undefined,
|
|
col: u32 = undefined,
|
|
source_line: ?[]const u8 = null,
|
|
span_main: u32 = undefined,
|
|
|
|
fn init(allocator: std.mem.Allocator) MsgWriter {
|
|
return .{
|
|
.buf = std.ArrayList(u8).init(allocator),
|
|
};
|
|
}
|
|
|
|
fn deinit(m: *MsgWriter) void {
|
|
m.buf.deinit();
|
|
}
|
|
|
|
fn resetRetainingCapacity(m: *MsgWriter) void {
|
|
m.buf.clearRetainingCapacity();
|
|
m.path = null;
|
|
m.source_line = null;
|
|
}
|
|
|
|
pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void {
|
|
m.buf.writer().print(fmt, args) catch {};
|
|
}
|
|
|
|
pub fn write(m: *MsgWriter, msg: []const u8) void {
|
|
m.buf.writer().writeAll(msg) catch {};
|
|
}
|
|
|
|
pub fn setColor(m: *MsgWriter, color: std.io.tty.Color) void {
|
|
_ = m;
|
|
_ = color;
|
|
}
|
|
|
|
pub fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void {
|
|
m.path = path;
|
|
m.line = line;
|
|
m.col = col;
|
|
}
|
|
|
|
pub fn start(m: *MsgWriter, kind: aro.Diagnostics.Kind) void {
|
|
_ = m;
|
|
_ = kind;
|
|
}
|
|
|
|
pub fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void {
|
|
_ = end_with_splice;
|
|
m.source_line = maybe_line;
|
|
m.span_main = col;
|
|
}
|
|
};
|