mirror of
https://github.com/odin-lang/Odin.git
synced 2026-10-09 06:12:05 -04:00
Add optional annotations for the next error source line.
This commit is contained in:
1 parent
f65f3cd4da
commit
614b45e919
2 files changed
+190
-6
No files matched your search
+57
-2
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user