| ... | @@ -338,6 +338,8 @@ pub const AllErrors = struct { | ... | @@ -338,6 +338,8 @@ pub const AllErrors = struct { |
| 338 | line: u32, | 338 | line: u32, |
| 339 | column: u32, | 339 | column: u32, |
| 340 | byte_offset: u32, | 340 | byte_offset: u32, |
| | 341 | /// Usually one, but incremented for redundant messages. |
| | 342 | count: u32 = 1, |
| 341 | /// Does not include the trailing newline. | 343 | /// Does not include the trailing newline. |
| 342 | source_line: ?[]const u8, | 344 | source_line: ?[]const u8, |
| 343 | notes: []Message = &.{}, | 345 | notes: []Message = &.{}, |
| ... | @@ -345,8 +347,21 @@ pub const AllErrors = struct { | ... | @@ -345,8 +347,21 @@ pub const AllErrors = struct { |
| 345 | plain: struct { | 347 | plain: struct { |
| 346 | msg: []const u8, | 348 | msg: []const u8, |
| 347 | notes: []Message = &.{}, | 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 | pub fn renderToStdErr(msg: Message, ttyconf: std.debug.TTY.Config) void { | 365 | pub fn renderToStdErr(msg: Message, ttyconf: std.debug.TTY.Config) void { |
| 351 | std.debug.getStderrMutex().lock(); | 366 | std.debug.getStderrMutex().lock(); |
| 352 | defer std.debug.getStderrMutex().unlock(); | 367 | defer std.debug.getStderrMutex().unlock(); |
| ... | @@ -376,7 +391,13 @@ pub const AllErrors = struct { | ... | @@ -376,7 +391,13 @@ pub const AllErrors = struct { |
| 376 | try stderr.writeAll(kind); | 391 | try stderr.writeAll(kind); |
| 377 | ttyconf.setColor(stderr, .Reset); | 392 | ttyconf.setColor(stderr, .Reset); |
| 378 | ttyconf.setColor(stderr, .Bold); | 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 | ttyconf.setColor(stderr, .Reset); | 401 | ttyconf.setColor(stderr, .Reset); |
| 381 | if (ttyconf != .no_color) { | 402 | if (ttyconf != .no_color) { |
| 382 | if (src.source_line) |line| { | 403 | if (src.source_line) |line| { |
| ... | @@ -400,7 +421,13 @@ pub const AllErrors = struct { | ... | @@ -400,7 +421,13 @@ pub const AllErrors = struct { |
| 400 | try stderr.writeByteNTimes(' ', indent); | 421 | try stderr.writeByteNTimes(' ', indent); |
| 401 | try stderr.writeAll(kind); | 422 | try stderr.writeAll(kind); |
| 402 | ttyconf.setColor(stderr, .Reset); | 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 | ttyconf.setColor(stderr, .Reset); | 431 | ttyconf.setColor(stderr, .Reset); |
| 405 | for (plain.notes) |note| { | 432 | for (plain.notes) |note| { |
| 406 | try note.renderToStdErrInner(ttyconf, stderr_file, "error:", .Red, indent + 4); | 433 | try note.renderToStdErrInner(ttyconf, stderr_file, "error:", .Red, indent + 4); |
| ... | @@ -408,6 +435,50 @@ pub const AllErrors = struct { | ... | @@ -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 | pub fn deinit(self: *AllErrors, gpa: Allocator) void { | 484 | pub fn deinit(self: *AllErrors, gpa: Allocator) void { |
| ... | @@ -421,13 +492,25 @@ pub const AllErrors = struct { | ... | @@ -421,13 +492,25 @@ pub const AllErrors = struct { |
| 421 | module_err_msg: Module.ErrorMsg, | 492 | module_err_msg: Module.ErrorMsg, |
| 422 | ) !void { | 493 | ) !void { |
| 423 | const allocator = arena.allocator(); | 494 | const allocator = arena.allocator(); |
| 424 | const notes = try allocator.alloc(Message, module_err_msg.notes.len); | 495 | |
| 425 | for (notes) |*note, i| { | 496 | const notes_buf = try allocator.alloc(Message, module_err_msg.notes.len); |
| 426 | const module_note = module_err_msg.notes[i]; | 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 | const source = try module_note.src_loc.file_scope.getSource(module.gpa); | 509 | const source = try module_note.src_loc.file_scope.getSource(module.gpa); |
| 428 | const byte_offset = try module_note.src_loc.byteOffset(module.gpa); | 510 | const byte_offset = try module_note.src_loc.byteOffset(module.gpa); |
| 429 | const loc = std.zig.findLineColumn(source.bytes, byte_offset); | 511 | const loc = std.zig.findLineColumn(source.bytes, byte_offset); |
| 430 | const file_path = try module_note.src_loc.file_scope.fullPath(allocator); | 512 | const file_path = try module_note.src_loc.file_scope.fullPath(allocator); |
| | 513 | const note = &notes_buf[note_i]; |
| 431 | note.* = .{ | 514 | note.* = .{ |
| 432 | .src = .{ | 515 | .src = .{ |
| 433 | .src_path = file_path, | 516 | .src_path = file_path, |
| ... | @@ -438,6 +521,12 @@ pub const AllErrors = struct { | ... | @@ -438,6 +521,12 @@ pub const AllErrors = struct { |
| 438 | .source_line = try allocator.dupe(u8, loc.source_line), | 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 | if (module_err_msg.src_loc.lazy == .entire_file) { | 531 | if (module_err_msg.src_loc.lazy == .entire_file) { |
| 443 | try errors.append(.{ | 532 | try errors.append(.{ |
| ... | @@ -458,7 +547,7 @@ pub const AllErrors = struct { | ... | @@ -458,7 +547,7 @@ pub const AllErrors = struct { |
| 458 | .byte_offset = byte_offset, | 547 | .byte_offset = byte_offset, |
| 459 | .line = @intCast(u32, loc.line), | 548 | .line = @intCast(u32, loc.line), |
| 460 | .column = @intCast(u32, loc.column), | 549 | .column = @intCast(u32, loc.column), |
| 461 | .notes = notes, | 550 | .notes = notes_buf[0..note_i], |
| 462 | .source_line = try allocator.dupe(u8, loc.source_line), | 551 | .source_line = try allocator.dupe(u8, loc.source_line), |
| 463 | }, | 552 | }, |
| 464 | }); | 553 | }); |