Fix some error reporting bugs

This commit is contained in:
Richard Feldman 2025-11-25 19:02:56 -05:00
parent b8c77f591e
commit bc178fa664
No known key found for this signature in database
3 changed files with 176 additions and 12 deletions

View file

@ -656,11 +656,11 @@ pub const Store = struct {
break :blk self.snapshots.sliceVars(fn_args)[0];
};
try self.writeWithContext(idx, .General, dispatcher);
try self.writeWithContext(dispatcher, .General, idx);
_ = try self.buf.writer().write(".");
_ = try self.buf.writer().write(self.idents.getText(constraint.fn_name));
_ = try self.buf.writer().write(" : ");
try self.writeWithContext(idx, .General, constraint.fn_content);
try self.writeWithContext(constraint.fn_content, .General, idx);
}
_ = try self.buf.writer().write("]");
}
@ -1048,15 +1048,77 @@ pub const Store = struct {
}
/// Append a constraint to the list, if it doesn't already exist
/// Deduplicates based on method name and dispatcher type variable
fn appendStaticDispatchConstraint(self: *Self, constraint_to_add: SnapshotStaticDispatchConstraint) std.mem.Allocator.Error!void {
// Extract dispatcher (first arg) identity from the constraint to add
const add_dispatcher = self.getDispatcherIdentity(constraint_to_add.fn_content);
for (self.static_dispatch_constraints.items) |constraint| {
if (constraint.fn_name == constraint_to_add.fn_name and constraint.fn_content == constraint_to_add.fn_content) {
return;
if (constraint.fn_name == constraint_to_add.fn_name) {
// Same method name - check if dispatcher identity is the same
const existing_dispatcher = self.getDispatcherIdentity(constraint.fn_content);
if (dispatcherIdentitiesEqual(add_dispatcher, existing_dispatcher)) {
return; // Duplicate constraint
}
// Also check fn_content directly for backwards compatibility
if (constraint.fn_content == constraint_to_add.fn_content) {
return;
}
}
}
_ = try self.static_dispatch_constraints.append(constraint_to_add);
}
/// Dispatcher identity for deduplication - either a Var (for flex), an Ident.Idx (for rigid), or recursive
const DispatcherIdentity = union(enum) {
flex_var: Var,
rigid_name: Ident.Idx,
recursive: void, // All recursive types are equivalent for deduplication
};
/// Get the dispatcher (first argument) identity from a function content
fn getDispatcherIdentity(self: *Self, fn_content: SnapshotContentIdx) ?DispatcherIdentity {
const content = self.snapshots.getContent(fn_content);
if (content != .structure) return null;
const fn_args = switch (content.structure) {
.fn_effectful => |func| func.args,
.fn_pure => |func| func.args,
.fn_unbound => |func| func.args,
else => return null,
};
if (fn_args.len() == 0) return null;
const first_arg_idx = self.snapshots.sliceVars(fn_args)[0];
const first_arg_content = self.snapshots.getContent(first_arg_idx);
return switch (first_arg_content) {
.flex => |flex| DispatcherIdentity{ .flex_var = flex.var_ },
.rigid => |rigid| DispatcherIdentity{ .rigid_name = rigid.name },
.recursive => DispatcherIdentity{ .recursive = {} },
else => null,
};
}
fn dispatcherIdentitiesEqual(a: ?DispatcherIdentity, b: ?DispatcherIdentity) bool {
if (a == null or b == null) return false;
return switch (a.?) {
.flex_var => |a_var| switch (b.?) {
.flex_var => |b_var| a_var == b_var,
else => false,
},
.rigid_name => |a_name| switch (b.?) {
.rigid_name => |b_name| a_name == b_name,
else => false,
},
.recursive => switch (b.?) {
.recursive => true, // All recursive types are equal
else => false,
},
};
}
/// Generate a name for a flex var that may appear multiple times in the type
fn writeFlexVarName(self: *Self, flex_var: Var, _: SnapshotContentIdx, context: TypeContext, root_idx: SnapshotContentIdx) std.mem.Allocator.Error!void {
// Check if we've seen this flex var before.
@ -1464,11 +1526,11 @@ pub const SnapshotWriter = struct {
break :blk self.snapshots.sliceVars(fn_args)[0];
};
try self.writeWithContext(idx, .General, dispatcher);
try self.writeWithContext(dispatcher, .General, idx);
_ = try self.buf.writer().write(".");
_ = try self.buf.writer().write(self.idents.getText(constraint.fn_name));
_ = try self.buf.writer().write(" : ");
try self.writeWithContext(idx, .General, constraint.fn_content);
try self.writeWithContext(constraint.fn_content, .General, idx);
}
_ = try self.buf.writer().write("]");
}
@ -1856,15 +1918,77 @@ pub const SnapshotWriter = struct {
}
/// Append a constraint to the list, if it doesn't already exist
/// Deduplicates based on method name and dispatcher type variable
fn appendStaticDispatchConstraint(self: *Self, constraint_to_add: SnapshotStaticDispatchConstraint) std.mem.Allocator.Error!void {
// Extract dispatcher (first arg) identity from the constraint to add
const add_dispatcher = self.getDispatcherIdentity(constraint_to_add.fn_content);
for (self.static_dispatch_constraints.items) |constraint| {
if (constraint.fn_name == constraint_to_add.fn_name and constraint.fn_content == constraint_to_add.fn_content) {
return;
if (constraint.fn_name == constraint_to_add.fn_name) {
// Same method name - check if dispatcher identity is the same
const existing_dispatcher = self.getDispatcherIdentity(constraint.fn_content);
if (dispatcherIdentitiesEqual(add_dispatcher, existing_dispatcher)) {
return; // Duplicate constraint
}
// Also check fn_content directly for backwards compatibility
if (constraint.fn_content == constraint_to_add.fn_content) {
return;
}
}
}
_ = try self.static_dispatch_constraints.append(constraint_to_add);
}
/// Dispatcher identity for deduplication - either a Var (for flex), an Ident.Idx (for rigid), or recursive
const DispatcherIdentity = union(enum) {
flex_var: Var,
rigid_name: Ident.Idx,
recursive: void, // All recursive types are equivalent for deduplication
};
/// Get the dispatcher (first argument) identity from a function content
fn getDispatcherIdentity(self: *Self, fn_content: SnapshotContentIdx) ?DispatcherIdentity {
const content = self.snapshots.getContent(fn_content);
if (content != .structure) return null;
const fn_args = switch (content.structure) {
.fn_effectful => |func| func.args,
.fn_pure => |func| func.args,
.fn_unbound => |func| func.args,
else => return null,
};
if (fn_args.len() == 0) return null;
const first_arg_idx = self.snapshots.sliceVars(fn_args)[0];
const first_arg_content = self.snapshots.getContent(first_arg_idx);
return switch (first_arg_content) {
.flex => |flex| DispatcherIdentity{ .flex_var = flex.var_ },
.rigid => |rigid| DispatcherIdentity{ .rigid_name = rigid.name },
.recursive => DispatcherIdentity{ .recursive = {} },
else => null,
};
}
fn dispatcherIdentitiesEqual(a: ?DispatcherIdentity, b: ?DispatcherIdentity) bool {
if (a == null or b == null) return false;
return switch (a.?) {
.flex_var => |a_var| switch (b.?) {
.flex_var => |b_var| a_var == b_var,
else => false,
},
.rigid_name => |a_name| switch (b.?) {
.rigid_name => |b_name| a_name == b_name,
else => false,
},
.recursive => switch (b.?) {
.recursive => true, // All recursive types are equal
else => false,
},
};
}
/// Generate a name for a flex var that may appear multiple times in the type
fn writeFlexVarName(self: *Self, flex_var: Var, _: SnapshotContentIdx, context: TypeContext, root_idx: SnapshotContentIdx) std.mem.Allocator.Error!void {
// Check if we've seen this flex var before.

View file

@ -298,8 +298,8 @@ fn renderElementToTerminal(element: DocumentElement, writer: *std.Io.Writer, pal
try writer.writeAll("");
try writer.writeAll(palette.reset);
// Print spaces up to the start column
try source_region.printSpaces(writer, region.start_column - 1);
// Print leading whitespace, preserving tabs from the source line
try source_region.printLeadingWhitespace(writer, line, region.start_column);
// Print the underline
try writer.writeAll(color);
@ -370,9 +370,15 @@ fn renderElementToTerminal(element: DocumentElement, writer: *std.Io.Writer, pal
var col_position: u32 = 1;
for (data.underline_regions) |underline| {
if (underline.start_line == line_num and underline.start_line == underline.end_line) {
// Print spaces up to the start column
// Print whitespace up to the start column
if (underline.start_column > col_position) {
try source_region.printSpaces(writer, underline.start_column - col_position);
if (col_position == 1) {
// First underline: preserve tabs from source
try source_region.printLeadingWhitespace(writer, line, underline.start_column);
} else {
// Subsequent underlines: just use spaces
try source_region.printSpaces(writer, underline.start_column - col_position);
}
}
// Print the underline

View file

@ -32,6 +32,40 @@ pub fn printSpaces(writer: anytype, count: u32) !void {
}
}
/// Print leading whitespace from source line, preserving tabs.
/// Copies exact whitespace characters (tabs/spaces) from the line,
/// and uses spaces for non-whitespace characters.
/// This ensures underlines align correctly when the source contains tabs.
///
/// Parameters:
/// - writer: The output writer
/// - line: The source line text
/// - target_column: The 1-based column to print up to (exclusive)
pub fn printLeadingWhitespace(writer: anytype, line: []const u8, target_column: u32) !void {
if (target_column <= 1) return;
const chars_to_print = target_column - 1;
var i: u32 = 0;
while (i < chars_to_print) : (i += 1) {
if (i < line.len) {
const char = line[i];
if (char == '\t') {
// Preserve tabs exactly
try writer.writeAll("\t");
} else if (char == ' ') {
// Preserve spaces exactly
try writer.writeAll(" ");
} else {
// Non-whitespace: use a space to maintain width
try writer.writeAll(" ");
}
} else {
// Past end of line: use spaces
try writer.writeAll(" ");
}
}
}
// ===== TESTS =====
test "calculateLineNumberWidth" {