Begin work on deferring untyped compound literals in polymorphic call contexts

This commit is contained in:
gingerBill committed 2026-09-30 23:00:17 +01:00
1 parent 12ba8a9d83
commit bbb2cf19a2
3 files changed
+110 -5

No files matched your search

+81 -5
View File
@@ -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 &parameter
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<Operand> positional_operands, Array<Operand> 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);
}
+1
View File
@@ -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
};
+28
View File
@@ -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);