From bbb2cf19a2f4510979b7f60f9fb9ef5347690f77 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Wed, 30 Sep 2026 23:00:17 +0100 Subject: [PATCH] Begin work on deferring untyped compound literals in polymorphic call contexts --- src/check_expr.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++--- src/checker.hpp | 1 + src/error.cpp | 28 +++++++++++++++ 3 files changed, 110 insertions(+), 5 deletions(-) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 305e3fa68..a2d052448 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -6686,6 +6686,10 @@ enum UnpackFlag : u32 { UnpackFlag_None = 0, UnpackFlag_AllowOk = 1<<0, UnpackFlag_AllowUndef = 1<<1, + // For calls to polymorphic procedures: a bare `{...}` argument with no concrete type hint is left + // unchecked as a deferred operand instead of erroring, to be resolved once the poly parameter's + // type is known (see materialize_deferred_compound_lit). + UnpackFlag_DeferUntypedCompoundLit = 1<<2, }; @@ -6756,7 +6760,18 @@ gb_internal bool check_unpack_arguments(CheckerContext *ctx, Entity **lhs, isize } Ast *rhs_expr = unparen_expr(rhs); - if (allow_undef && rhs_expr != nullptr && rhs_expr->kind == Ast_Uninit) { + bool defer_untyped_compound_lit = + (flags & UnpackFlag_DeferUntypedCompoundLit) != 0 && + rhs_expr != nullptr && rhs_expr->kind == Ast_CompoundLit && rhs_expr->CompoundLit.type == nullptr && + (type_hint == nullptr || is_type_polymorphic(type_hint)); + if (defer_untyped_compound_lit) { + // NOTE: Leave the literal unchecked; its type comes from the resolved poly parameter later. + // Kept as an "invalid" operand so polymorphic determination treats it as carrying no info. + o.mode = Addressing_Invalid; + o.type = t_invalid; + o.expr = rhs; + o.deferred_compound_lit = true; + } else if (allow_undef && rhs_expr != nullptr && rhs_expr->kind == Ast_Uninit) { // NOTE(bill): Just handle this very specific logic here o.type = t_untyped_uninit; o.mode = Addressing_Value; @@ -6891,6 +6906,39 @@ gb_internal isize lookup_procedure_parameter(Type *type, String const ¶meter return lookup_procedure_parameter(&type->Proc, parameter_name); } +// Resolve a deferred untyped compound-literal argument now that its parameter type is known. +// `param_type` is the concrete (post-substitution) parameter/element type for the slot. On success the +// operand is checked against it as if it had been written with that type; on failure it is marked +// invalid. When !show_error the check is muted (trial scoring of a procedure-group candidate) but its +// failure is still recorded, so the candidate can be rejected without printing anything. +gb_internal void materialize_deferred_compound_lit(CheckerContext *c, Operand *o, Type *param_type, bool show_error) { + GB_ASSERT(o->deferred_compound_lit); + o->deferred_compound_lit = false; + Ast *lit = o->expr; + + if (param_type == nullptr || param_type == t_invalid || is_type_polymorphic(param_type)) { + // The slot's type could not be determined from the other arguments, so the literal is genuinely + // untypable here (e.g. `proc(e: $E)` called with a bare `{...}`). Leave it invalid; polymorphic + // determination has already reported why the type could not be resolved. + o->mode = Addressing_Invalid; + o->type = t_invalid; + return; + } + + if (show_error) { + check_expr_base(c, o, lit, param_type); + } else { + i64 muted_before = error_mute_count(); + begin_error_mute(); + check_expr_base(c, o, lit, param_type); + end_error_mute(); + if (error_mute_count() != muted_before) { + o->mode = Addressing_Invalid; + o->type = t_invalid; + } + } +} + gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, Ast *call, Entity *entity, Type *proc_type, Array positional_operands, Array const &named_operands, @@ -7232,6 +7280,9 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A for (isize i = 0; i < pt->param_count; i++) { Operand *o = &ordered_operands[i]; + if (o->deferred_compound_lit) { + materialize_deferred_compound_lit(c, o, pt->params->Tuple.variables[i]->type, show_error); + } if (o->mode == Addressing_Invalid) { continue; } @@ -7280,6 +7331,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A for_array(operand_index, variadic_operands) { Operand *o = &variadic_operands[operand_index]; + Operand deferred_local; + if (o->deferred_compound_lit) { + // `variadic_operands` aliases the shared operand buffer reused across candidates, so + // resolve into a local copy rather than mutating it in place. + deferred_local = *o; + materialize_deferred_compound_lit(c, &deferred_local, vari_expand ? slice : elem, show_error); + o = &deferred_local; + } if (vari_expand) { t = slice; if (operand_index > 0) { @@ -8391,13 +8450,17 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op Entity **lhs = nullptr; isize lhs_count = -1; i32 variadic_index = -1; + UnpackFlags unpack_flags = UnpackFlag_None; if (pt != nullptr) { lhs = populate_proc_parameter_list(c, proc_type, &lhs_count); if (pt->variadic) { variadic_index = pt->variadic_index; } + if (pt->is_polymorphic) { + unpack_flags |= UnpackFlag_DeferUntypedCompoundLit; + } } - check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, UnpackFlag_None, variadic_index); + check_unpack_arguments(c, lhs, lhs_count, &positional_operands, positional_args, unpack_flags, variadic_index); } if (named_args.count > 0) { @@ -8426,9 +8489,22 @@ gb_internal CallArgumentData check_call_arguments(CheckerContext *c, Operand *op } Operand o = {}; - check_expr_with_type_hint(c, &o, value, type_hint); - if (o.mode == Addressing_Invalid) { - any_failure = true; + Ast *value_expr = unparen_expr(value); + bool defer_untyped_compound_lit = + pt != nullptr && pt->is_polymorphic && + value_expr != nullptr && value_expr->kind == Ast_CompoundLit && value_expr->CompoundLit.type == nullptr && + (type_hint == nullptr || is_type_polymorphic(type_hint)); + if (defer_untyped_compound_lit) { + // Mirror the positional path: resolve the literal from the poly parameter's type later. + o.mode = Addressing_Invalid; + o.type = t_invalid; + o.expr = value; + o.deferred_compound_lit = true; + } else { + check_expr_with_type_hint(c, &o, value, type_hint); + if (o.mode == Addressing_Invalid) { + any_failure = true; + } } array_add(&named_operands, o); } diff --git a/src/checker.hpp b/src/checker.hpp index 81bcc8e25..9781e4c82 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -85,6 +85,7 @@ struct Operand { Ast * expr; BuiltinProcId builtin_id; Entity * proc_group; + bool deferred_compound_lit; // untyped `{...}` arg whose type is resolved from a poly param later }; diff --git a/src/error.cpp b/src/error.cpp index 277081459..3f7cb4aae 100644 --- a/src/error.cpp +++ b/src/error.cpp @@ -27,6 +27,26 @@ struct ErrorCollector { gb_global ErrorCollector global_error_collector; +// 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. +gb_thread_local i32 global_error_mute_depth = 0; +gb_thread_local i64 global_error_mute_count = 0; + +gb_internal void begin_error_mute(void) { + global_error_mute_depth += 1; +} +gb_internal void end_error_mute(void) { + GB_ASSERT(global_error_mute_depth > 0); + global_error_mute_depth -= 1; +} +gb_internal i64 error_mute_count(void) { + return global_error_mute_count; +} +gb_internal bool is_error_muted(void) { + return global_error_mute_depth > 0; +} + 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"); @@ -542,6 +562,10 @@ gb_internal void error_out_coloured(char const *str, TerminalStyle style, Termin gb_internal void error_va(TokenPos const &pos, TokenPos end, char const *fmt, va_list va) { + if (global_error_mute_depth > 0) { + global_error_mute_count += 1; + return; + } global_error_collector.count.fetch_add(1); mutex_lock(&global_error_collector.mutex); if (global_error_collector.count > MAX_ERROR_COLLECTOR_COUNT()) { @@ -579,6 +603,10 @@ gb_internal void warning_va(TokenPos const &pos, TokenPos end, char const *fmt, if (global_ignore_warnings()) { return; } + if (global_error_mute_depth > 0) { + global_error_mute_count += 1; + return; + } global_error_collector.warning_count.fetch_add(1); mutex_lock(&global_error_collector.mutex);