diff --git a/src/check_expr.cpp b/src/check_expr.cpp index c4d7ab8d9..52b731efb 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -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) { diff --git a/src/error.cpp b/src/error.cpp index 3f7cb4aae..77fef3cea 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -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; }