| ... | ... | @@ -338,6 +338,8 @@ pub const AllErrors = struct { |
| 338 | 338 | line: u32, |
| 339 | 339 | column: u32, |
| 340 | 340 | byte_offset: u32, |
| 341 | /// Usually one, but incremented for redundant messages. |
| 342 | count: u32 = 1, |
| 341 | 343 | /// Does not include the trailing newline. |
| 342 | 344 | source_line: ?[]const u8, |
| 343 | 345 | notes: []Message = &.{}, |
| ... | ... | @@ -345,8 +347,21 @@ pub const AllErrors = struct { |
| 345 | 347 | plain: struct { |
| 346 | 348 | msg: []const u8, |
| 347 | 349 | notes: []Message = &.{}, |
| 350 | /// Usually one, but incremented for redundant messages. |
| 351 | count: u32 = 1, |
| 348 | 352 | }, |
| 349 | 353 | |
| 354 | pub fn incrementCount(msg: *Message) void { |
| 355 | switch (msg.*) { |
| 356 | .src => |*src| { |
| 357 | src.count += 1; |
| 358 | }, |
| 359 | .plain => |*plain| { |
| 360 | plain.count += 1; |
| 361 | }, |
| 362 | } |
| 363 | } |
| 364 | |
| 350 | 365 | pub fn renderToStdErr(msg: Message, ttyconf: std.debug.TTY.Config) void { |
| 351 | 366 | std.debug.getStderrMutex().lock(); |
| 352 | 367 | defer std.debug.getStderrMutex().unlock(); |
| ... | ... | @@ -376,7 +391,13 @@ pub const AllErrors = struct { |
| 376 | 391 | try stderr.writeAll(kind); |
| 377 | 392 | ttyconf.setColor(stderr, .Reset); |
| 378 | 393 | ttyconf.setColor(stderr, .Bold); |
| 379 | | try stderr.print(" {s}\n", .{src.msg}); |
| 394 | if (src.count == 1) { |
| 395 | try stderr.print(" {s}\n", .{src.msg}); |
| 396 | } else { |
| 397 | try stderr.print(" {s}", .{src.msg}); |
| 398 | ttyconf.setColor(stderr, .Dim); |
| 399 | try stderr.print(" ({d} times)\n", .{src.count}); |
| 400 | } |
| 380 | 401 | ttyconf.setColor(stderr, .Reset); |
| 381 | 402 | if (ttyconf != .no_color) { |
| 382 | 403 | if (src.source_line) |line| { |
| ... | ... | @@ -400,7 +421,13 @@ pub const AllErrors = struct { |
| 400 | 421 | try stderr.writeByteNTimes(' ', indent); |
| 401 | 422 | try stderr.writeAll(kind); |
| 402 | 423 | ttyconf.setColor(stderr, .Reset); |
| 403 | | try stderr.print(" {s}\n", .{plain.msg}); |
| 424 | if (plain.count == 1) { |
| 425 | try stderr.print(" {s}\n", .{plain.msg}); |
| 426 | } else { |
| 427 | try stderr.print(" {s}", .{plain.msg}); |
| 428 | ttyconf.setColor(stderr, .Dim); |
| 429 | try stderr.print(" ({d} times)\n", .{plain.count}); |
| 430 | } |
| 404 | 431 | ttyconf.setColor(stderr, .Reset); |
| 405 | 432 | for (plain.notes) |note| { |
| 406 | 433 | try note.renderToStdErrInner(ttyconf, stderr_file, "error:", .Red, indent + 4); |
| ... | ... | @@ -408,6 +435,50 @@ pub const AllErrors = struct { |
| 408 | 435 | }, |
| 409 | 436 | } |
| 410 | 437 | } |
| 438 | |
| 439 | pub const HashContext = struct { |
| 440 | pub fn hash(ctx: HashContext, key: *Message) u64 { |
| 441 | _ = ctx; |
| 442 | var hasher = std.hash.Wyhash.init(0); |
| 443 | |
| 444 | switch (key.*) { |
| 445 | .src => |src| { |
| 446 | hasher.update(src.msg); |
| 447 | hasher.update(src.src_path); |
| 448 | std.hash.autoHash(&hasher, src.line); |
| 449 | std.hash.autoHash(&hasher, src.column); |
| 450 | std.hash.autoHash(&hasher, src.byte_offset); |
| 451 | }, |
| 452 | .plain => |plain| { |
| 453 | hasher.update(plain.msg); |
| 454 | }, |
| 455 | } |
| 456 | |
| 457 | return hasher.final(); |
| 458 | } |
| 459 | |
| 460 | pub fn eql(ctx: HashContext, a: *Message, b: *Message) bool { |
| 461 | _ = ctx; |
| 462 | switch (a.*) { |
| 463 | .src => |a_src| switch (b.*) { |
| 464 | .src => |b_src| { |
| 465 | return mem.eql(u8, a_src.msg, b_src.msg) and |
| 466 | mem.eql(u8, a_src.src_path, b_src.src_path) and |
| 467 | a_src.line == b_src.line and |
| 468 | a_src.column == b_src.column and |
| 469 | a_src.byte_offset == b_src.byte_offset; |
| 470 | }, |
| 471 | .plain => return false, |
| 472 | }, |
| 473 | .plain => |a_plain| switch (b.*) { |
| 474 | .src => return false, |
| 475 | .plain => |b_plain| { |
| 476 | return mem.eql(u8, a_plain.msg, b_plain.msg); |
| 477 | }, |
| 478 | }, |
| 479 | } |
| 480 | } |
| 481 | }; |
| 411 | 482 | }; |
| 412 | 483 | |
| 413 | 484 | pub fn deinit(self: *AllErrors, gpa: Allocator) void { |
| ... | ... | @@ -421,13 +492,25 @@ pub const AllErrors = struct { |
| 421 | 492 | module_err_msg: Module.ErrorMsg, |
| 422 | 493 | ) !void { |
| 423 | 494 | const allocator = arena.allocator(); |
| 424 | | const notes = try allocator.alloc(Message, module_err_msg.notes.len); |
| 425 | | for (notes) |*note, i| { |
| 426 | | const module_note = module_err_msg.notes[i]; |
| 495 | |
| 496 | const notes_buf = try allocator.alloc(Message, module_err_msg.notes.len); |
| 497 | var note_i: usize = 0; |
| 498 | |
| 499 | // De-duplicate error notes. The main use case in mind for this is |
| 500 | // too many "note: called from here" notes when eval branch quota is reached. |
| 501 | var seen_notes = std.HashMap( |
| 502 | *Message, |
| 503 | void, |
| 504 | Message.HashContext, |
| 505 | std.hash_map.default_max_load_percentage, |
| 506 | ).init(allocator); |
| 507 | |
| 508 | for (module_err_msg.notes) |module_note| { |
| 427 | 509 | const source = try module_note.src_loc.file_scope.getSource(module.gpa); |
| 428 | 510 | const byte_offset = try module_note.src_loc.byteOffset(module.gpa); |
| 429 | 511 | const loc = std.zig.findLineColumn(source.bytes, byte_offset); |
| 430 | 512 | const file_path = try module_note.src_loc.file_scope.fullPath(allocator); |
| 513 | const note = &notes_buf[note_i]; |
| 431 | 514 | note.* = .{ |
| 432 | 515 | .src = .{ |
| 433 | 516 | .src_path = file_path, |
| ... | ... | @@ -438,6 +521,12 @@ pub const AllErrors = struct { |
| 438 | 521 | .source_line = try allocator.dupe(u8, loc.source_line), |
| 439 | 522 | }, |
| 440 | 523 | }; |
| 524 | const gop = try seen_notes.getOrPut(note); |
| 525 | if (gop.found_existing) { |
| 526 | gop.key_ptr.*.incrementCount(); |
| 527 | } else { |
| 528 | note_i += 1; |
| 529 | } |
| 441 | 530 | } |
| 442 | 531 | if (module_err_msg.src_loc.lazy == .entire_file) { |
| 443 | 532 | try errors.append(.{ |
| ... | ... | @@ -458,7 +547,7 @@ pub const AllErrors = struct { |
| 458 | 547 | .byte_offset = byte_offset, |
| 459 | 548 | .line = @intCast(u32, loc.line), |
| 460 | 549 | .column = @intCast(u32, loc.column), |
| 461 | | .notes = notes, |
| 550 | .notes = notes_buf[0..note_i], |
| 462 | 551 | .source_line = try allocator.dupe(u8, loc.source_line), |
| 463 | 552 | }, |
| 464 | 553 | }); |