diff --git a/src/check_decl.cpp b/src/check_decl.cpp index b2a3e28e3..06b832311 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -2219,10 +2219,16 @@ gb_internal void check_entity_decl(CheckerContext *ctx, Entity *e, DeclInfo *d, } CheckerContext c = *ctx; + ProcInfo *prev_instantiation = global_error_instantiation; + ErrorRecordInstantiation *prev_record_instantiation = global_error_record_instantiation; + defer (global_error_instantiation = prev_instantiation); + defer (global_error_record_instantiation = prev_record_instantiation); if (d->scope->flags & ScopeFlag_File) { // NOTE(bill): a global is checked in a context of its own file, never in that of whatever needed it first, - // which may be in another file or package, or a procedure body. + // which may be in another file or package, or a procedure body (nor is it part of its instantiation). // Only the cycle detection carries over. + global_error_instantiation = nullptr; + global_error_record_instantiation = nullptr; CheckerTypePath *type_path = c.type_path; UntypedExprInfoMap *untyped = c.untyped; gb_zero_size(&c.pkg, gb_size_of(CheckerContext) - gb_offset_of(CheckerContext, pkg)); diff --git a/src/check_expr.cpp b/src/check_expr.cpp index ad6aff038..d71c98d3b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -860,6 +860,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E proc_info->tags = tags; proc_info->generated_from_polymorphic = true; proc_info->poly_def_node = poly_def_node; + proc_info->poly_parent = global_error_instantiation; // Before it can be found by another thread which could use it first d->gen_proc_info.store(proc_info); @@ -9172,6 +9173,8 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O String generated_name = make_string_c(expr_to_string(call)); Type *named_type = alloc_type_named(generated_name, nullptr, nullptr); + ErrorRecordInstantiation instantiation = {global_error_record_instantiation, call, named_type}; + global_error_record_instantiation = &instantiation; if (bt->kind == Type_Struct) { Ast *node = clone_ast(bt->Struct.node); Type *struct_type = alloc_type_struct(); @@ -9199,6 +9202,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O } else { GB_PANIC("Unsupported parametric polymorphic record type"); } + global_error_record_instantiation = instantiation.prev; add_declaration_dependency(c, named_type->Named.type_name); operand->mode = Addressing_Type; diff --git a/src/checker.cpp b/src/checker.cpp index eb46ac08e..435431847 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -2719,6 +2719,7 @@ gb_internal void check_procedure_later(Checker *c, AstFile *file, Token token, D info->type = type; info->body = body; info->tags = tags; + info->poly_parent = global_error_instantiation; check_procedure_later(c, info); } @@ -6695,6 +6696,70 @@ gb_internal WORKER_TASK_PROC(calculate_global_init_order_worker) { return 0; } +// NOTE(bill): Appends to the current error where each polymorphic instantiation it is within was instantiated +// e.g. +// main.odin(9:7) instantiated as 'add($T=bool)' +gb_internal void error_out_instantiations(void) { + isize const max_shown = 8; + isize count = 0; + for (ErrorRecordInstantiation *r = global_error_record_instantiation; r != nullptr; r = r->prev) { + if (count++ < max_shown) { + error_out("\t%s instantiated as '%.*s'\n", token_pos_to_string(ast_token(r->site).pos), LIT(r->named_type->Named.name)); + } + } + for (ProcInfo *pi = global_error_instantiation; pi != nullptr; pi = pi->poly_parent) { + if (count++ >= max_shown) { + continue; + } + + // NOTE(bill): the polymorphic parameters are the type names and constants declared before the body + Scope *scope = pi->type->Proc.scope; + i32 body_offset = ast_token(pi->body).pos.offset; + auto params = array_make(heap_allocator(), 0, 8); + defer (array_free(¶ms)); + rw_mutex_shared_lock(&scope->mutex); + for (auto const &entry : scope->elements) { + Entity *e = entry.value; + if (e == nullptr || (e->kind != Entity_TypeName && e->kind != Entity_Constant) || e->token.pos.offset >= body_offset) { + continue; + } + array_add(¶ms, e); + for (isize j = params.count-1; j > 0 && params[j-1]->token.pos.offset > e->token.pos.offset; j--) { + params[j] = params[j-1]; + params[j-1] = e; + } + } + rw_mutex_shared_unlock(&scope->mutex); + + gbString s = gb_string_make(heap_allocator(), ""); + defer (gb_string_free(s)); + s = gb_string_append_fmt(s, "%.*s(", LIT(pi->token.string)); + for_array(i, params) { + Entity *e = params[i]; + s = gb_string_append_fmt(s, "%s$%.*s", i > 0 ? ", " : "", LIT(e->token.string)); + if (e->kind == Entity_TypeName) { + if (e->type != nullptr && e->type->kind != Type_Generic) { + s = gb_string_append_fmt(s, "="); + s = write_type_to_string(s, e->type, false); + } + } else if (e->Constant.value.kind != ExactValue_Invalid) { + s = gb_string_append_fmt(s, "="); + s = write_exact_value_to_string(s, e->Constant.value); + } + } + s = gb_string_append_fmt(s, ")"); + + if (pi->poly_def_node != nullptr) { + error_out("\t%s instantiated as '%s'\n", token_pos_to_string(ast_token(pi->poly_def_node).pos), s); + } else { + error_out("\tinstantiated as '%s'\n", s); + } + } + if (count > max_shown) { + error_out("\t... and %td more\n", count - max_shown); + } +} + gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *untyped) { if (pi == nullptr) { return false; @@ -6774,7 +6839,10 @@ gb_internal bool check_proc_info(Checker *c, ProcInfo *pi, UntypedExprInfoMap *u ctx.state_flags &= ~StateFlag_type_assert; } + ProcInfo *prev_instantiation = global_error_instantiation; + global_error_instantiation = pi->generated_from_polymorphic ? pi : pi->poly_parent; bool body_was_checked = check_proc_body(&ctx, pi->token, pi->decl, pi->type, pi->body); + global_error_instantiation = prev_instantiation; if (body_was_checked) { pi->decl->proc_checked_state.store(ProcCheckedState_Checked); diff --git a/src/checker.hpp b/src/checker.hpp index 55eaeb27b..5c996b384 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -268,6 +268,7 @@ struct ProcInfo { u64 tags; bool generated_from_polymorphic; Ast * poly_def_node; + ProcInfo *poly_parent; // the instantiation whose body this was instantiated or declared in, see `error_out_instantiations` }; diff --git a/src/error.cpp b/src/error.cpp index 92f730955..0ea8ab77f 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -34,6 +34,18 @@ gb_global ErrorCollector global_error_collector; gb_thread_local bool global_error_hold = false; +struct ErrorRecordInstantiation { + ErrorRecordInstantiation *prev; + struct Ast * site; + struct Type * named_type; +}; + +// The polymorphic procedure instantiation whose body is being checked on this thread, +// and the polymorphic records being instantiated within it, see `error_out_instantiations` +gb_thread_local struct ProcInfo * global_error_instantiation = nullptr; +gb_thread_local ErrorRecordInstantiation *global_error_record_instantiation = nullptr; +gb_internal void error_out_instantiations(void); // defined in checker.cpp + // Scoped, per-thread error muting. While muted, error/warning emission is suppressed but still // *counted*, so a caller can trial-check something (e.g. one branch of a procedure group) and learn // whether it would have failed without printing anything. Muting nests. @@ -102,6 +114,10 @@ gb_internal void push_error_value(TokenPos const &pos, ErrorValueKind kind = Err gb_internal void pop_error_value(void) { mutex_lock(&global_error_collector.mutex); if (global_error_collector.curr_error_value_set.load()) { + if ((global_error_instantiation != nullptr || global_error_record_instantiation != nullptr) && + global_error_collector.curr_error_value.kind == ErrorValue_Error) { + error_out_instantiations(); + } array_add(global_error_hold ? &global_error_collector.held_error_values : &global_error_collector.error_values, global_error_collector.curr_error_value); global_error_collector.curr_error_value = {};