Add optional annotations for the next error source line.

This commit is contained in:
gingerBill committed 2026-10-01 00:49:36 +01:00
1 parent f65f3cd4da
commit 614b45e919
2 files changed
+190 -6

No files matched your search

+57 -2
View File
@@ -8204,14 +8204,69 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
}
}
// NOTE(bill): Missing `&`: a container argument passed by value where an overload wants a pointer
// to it. Detected only when the element-mismatch diagnosis did not fire. The `&x` suggestion is
// still printed below by the existing try-address block.
Ast *addr_expr = nullptr;
isize addr_index = -1;
if (diag_expr == nullptr) {
for (Entity *proc : procs) {
Type *t = base_type(proc->type);
if (t == nullptr || t->kind != Type_Proc || t->Proc.params == nullptr) {
continue;
}
isize n = gb_min(cast(isize)t->Proc.param_count, positional_operands.count);
for (isize i = 0; i < n; i++) {
Operand src = positional_operands[i];
if (src.deferred_untyped_arg || src.mode == Addressing_Invalid || src.type == nullptr) {
continue;
}
Type *dst = t->Proc.params->Tuple.variables[i]->type;
if (check_is_assignable_to(c, &src, dst)) {
continue;
}
if (check_is_assignable_to(c, &src, type_deref(dst))) {
addr_expr = src.expr;
addr_index = i;
break;
}
}
if (addr_expr != nullptr) {
break;
}
}
}
if (diag_expr != nullptr) {
gbString es = type_to_string(diag_elem);
error(diag_expr, "'%s' expected argument #%td to be of type '%s', got '%s'", expr_name, diag_index+1, es, diag_arg_type);
gbString cs = type_to_string(diag_cont);
error_line("\t'%s' is the element type of '%s'\n", es, cs);
gbString label = gb_string_make(heap_allocator(), "");
label = gb_string_append_fmt(label, "expected '%s', found '%s'", es, diag_arg_type);
set_caret_label(label);
// secondary span under the container argument, explaining where the element type comes from
Ast *cont_expr = positional_operands[0].expr;
gbString sec = gb_string_make(heap_allocator(), "");
sec = gb_string_append_fmt(sec, "'%s', elements are '%s'", cs, es);
if (cont_expr != nullptr) {
set_caret_secondary(ast_token(cont_expr).pos, ast_end_pos(cont_expr), sec);
}
error(diag_expr, "mismatched argument #%td in call to '%s'", diag_index+1, expr_name);
gb_string_free(sec);
gb_string_free(label);
gb_string_free(cs);
gb_string_free(es);
gb_string_free(diag_arg_type);
} else if (addr_expr != nullptr) {
Operand src = positional_operands[addr_index];
gbString ft = type_to_string(src.type);
gbString ex = expr_to_string(src.expr);
gbString label = gb_string_make(heap_allocator(), "");
label = gb_string_append_fmt(label, "expected '^%s', found '%s', pass '&%s'", ft, ft, ex);
set_caret_label_vertical(label);
error(src.expr, "mismatched argument #%td in call to '%s'", addr_index+1, expr_name);
gb_string_free(label);
gb_string_free(ex);
gb_string_free(ft);
} else {
error(operand->expr, "No procedures or ambiguous call for procedure group '%s' that match with the given arguments", expr_name);
if (positional_operands.count == 0 && named_operands.count == 0) {
+133 -4
View File
@@ -47,6 +47,41 @@ gb_internal bool is_error_muted(void) {
return global_error_mute_depth > 0;
}
// Optional annotations for the next errored source line.
// All per-thread, so nothing leaks between concurrently-checked files.
// `show_error_on_line` snapshots and clears them.
//
// primary label, inline: primary label, vertical: secondary span (always vertical):
// append(&x, true) append(x, {1,2,3}) append(&x, true)
// ^~~^ expected '…', found '…' ^ ^^ ^~~^ expected '…', found '…'
// | |
// expected '^…', pass '&x' '[dynamic][3]int', elements are '[3]int'
gb_thread_local char global_caret_label_buf[512]; // primary label text
gb_thread_local bool global_caret_label_vertical; // render primary label as | + text below, not inline
gb_thread_local bool global_caret_sec_present; // a secondary span (to the left) is set
gb_thread_local TokenPos global_caret_sec_pos;
gb_thread_local TokenPos global_caret_sec_end;
gb_thread_local char global_caret_sec_label[512];
gb_internal void set_caret_label(char const *text) {
global_caret_label_vertical = false;
if (text == nullptr) {
global_caret_label_buf[0] = 0;
} else {
gb_snprintf(global_caret_label_buf, gb_size_of(global_caret_label_buf), "%s", text);
}
}
gb_internal void set_caret_label_vertical(char const *text) {
set_caret_label(text);
global_caret_label_vertical = true;
}
gb_internal void set_caret_secondary(TokenPos pos, TokenPos end, char const *text) {
global_caret_sec_present = text != nullptr;
global_caret_sec_pos = pos;
global_caret_sec_end = end;
gb_snprintf(global_caret_sec_label, gb_size_of(global_caret_sec_label), "%s", text ? text : "");
}
gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = ErrorValue_Error) {
GB_ASSERT_MSG(global_error_collector.curr_error_value_set.load() == false, "Possible race condition in error handling system, please report this with an issue");
@@ -308,6 +343,22 @@ gb_internal void terminal_reset_colours(void) {
gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) {
get_error_value()->end = end;
// Consume the caret annotations now, so they are cleared even on an early return and can never leak
// onto a later error's line.
char caret_label[512];
gb_snprintf(caret_label, gb_size_of(caret_label), "%s", global_caret_label_buf);
bool caret_label_vertical = global_caret_label_vertical;
bool sec_present = global_caret_sec_present;
TokenPos sec_pos = global_caret_sec_pos;
TokenPos sec_end = global_caret_sec_end;
char sec_label[512];
gb_snprintf(sec_label, gb_size_of(sec_label), "%s", global_caret_sec_label);
global_caret_label_buf[0] = 0;
global_caret_label_vertical = false;
global_caret_sec_present = false;
global_caret_sec_label[0] = 0;
if (!show_error_line()) {
return -1;
}
@@ -512,13 +563,57 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) {
error_out(" ...");
}
error_out("\n\t");
for (i32 i = squiggle_padding; i > 0; i -= 1) {
error_out(" ");
// Secondary span (drawn to the left of the primary on the same line): compute its padding/width the
// same way as the primary's, and only draw it if it sits fully to the left within the window.
i32 sec_pad = 0;
i32 sec_len = 0;
bool draw_sec = sec_present && sec_pos.line == pos.line && sec_end.line == pos.line;
if (draw_sec) {
i32 sec_start_byte = error_start_index_bytes + (sec_pos.column - pos.column);
i32 sec_end_byte = error_start_index_bytes + (sec_end.column - pos.column);
if (window_open_bytes > 0) {
sec_pad += 4;
}
for (i32 i = 0; i < line_length_graphemes; i += 1) {
if (graphemes[i].byte_index < window_open_bytes) continue;
if (graphemes[i].byte_index >= sec_start_byte) break;
sec_pad += graphemes[i].width;
}
for (i32 i = 0; i < line_length_graphemes; i += 1) {
if (graphemes[i].byte_index < sec_start_byte) continue;
if (graphemes[i].byte_index >= sec_end_byte) break;
sec_len += graphemes[i].width;
}
if (sec_len < 1) {
sec_len = 1;
}
if (sec_start_byte >= error_start_index_bytes || sec_pad + sec_len > squiggle_padding) {
draw_sec = false; // overlaps or is not to the left; skip rather than misalign
}
}
error_out("\n\t");
i32 printed = 0;
terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green);
if (draw_sec) {
for (i32 i = sec_pad - printed; i > 0; i -= 1) {
error_out(" ");
}
printed = sec_pad;
error_out("^");
for (i32 k = sec_len - 2; k > 0; k -= 1) {
error_out("~");
}
if (sec_len >= 2) {
error_out("^");
}
printed += sec_len;
}
for (i32 i = squiggle_padding - printed; i > 0; i -= 1) {
error_out(" ");
}
if (squiggle_length > 0) {
error_out("^");
@@ -535,12 +630,46 @@ gb_internal isize show_error_on_line(TokenPos const &pos, TokenPos end) {
}
}
if (caret_label[0] != 0 && !caret_label_vertical) {
terminal_set_colours(TerminalStyle_Normal, TerminalColour_Grey);
error_out(" %s", caret_label);
}
// NOTE(Feoramund): Specifically print a newline, then reset colours,
// instead of the other way around. Otherwise the printing mechanism
// will collapse the newline for reasons currently beyond my ken.
error_out("\n");
terminal_reset_colours();
// A vertical label hangs under its span: `|` then the text, aligned to the span's column. The
// secondary span takes priority (its label explains the primary); otherwise a vertical primary label.
i32 vpad = -1;
char const *vtext = nullptr;
if (draw_sec && sec_label[0] != 0) {
vpad = sec_pad;
vtext = sec_label;
} else if (caret_label_vertical && caret_label[0] != 0) {
vpad = squiggle_padding;
vtext = caret_label;
}
if (vpad >= 0 && vtext != nullptr && vtext[0] != 0) {
error_out("\t");
for (i32 i = vpad; i > 0; i -= 1) {
error_out(" ");
}
terminal_set_colours(TerminalStyle_Bold, TerminalColour_Green);
error_out("|\n");
terminal_reset_colours();
error_out("\t");
for (i32 i = vpad; i > 0; i -= 1) {
error_out(" ");
}
terminal_set_colours(TerminalStyle_Normal, TerminalColour_Grey);
error_out("%s\n", vtext);
terminal_reset_colours();
}
return squiggle_padding;
}