Bind a $T whose argument has no definite type from the leftmost argument with one without changing which procedure group member a call picks

This commit is contained in:
gingerBill committed 2026-10-10 19:15:04 +01:00
1 parent d3eeb87cad
commit a88548cb6a
4 files changed
+179

No files matched your search

+25
View File
@@ -47,16 +47,19 @@ struct CallArgumentData {
Entity *gen_entity;
i64 score;
Type * result_type;
bool bound_by_later_arg;
};
struct PolyProcData {
Entity * gen_entity;
ProcInfo *proc_info;
bool bound_by_later_arg;
};
struct ValidIndexAndScore {
isize index;
i64 score;
bool bound_by_later_arg;
};
gb_internal int valid_index_and_score_cmp(void const *a, void const *b) {
@@ -126,6 +129,7 @@ gb_internal isize get_procedure_param_count_excluding_defaults(Type *pt, isize *
gb_internal bool is_expr_inferred_fixed_array(Ast *type_expr);
gb_internal Entity *find_polymorphic_record_entity(GenTypesData *found_gen_types, isize param_count, Array<Operand> const &ordered_operands);
gb_internal Array<Operand> poly_operands_with_definite_types(CheckerContext *c, TypeProc *pt, Array<Operand> const &operands);
gb_internal bool complete_soa_type(Checker *checker, Type *t, bool wait_to_finish);
@@ -658,6 +662,13 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E
Array<Operand> operands = {};
if (param_operands) {
operands = *param_operands;
Array<Operand> bound = poly_operands_with_definite_types(old_c, &src->Proc, operands);
if (bound.data != nullptr) {
operands = bound;
if (poly_proc_data) {
poly_proc_data->bound_by_later_arg = true;
}
}
} else {
operands = array_make<Operand>(a, 0, dst->Proc.param_count);
for (isize i = 0; i < dst->Proc.param_count; i++) {
@@ -7047,6 +7058,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
Type *final_proc_type = proc_type;
Entity *gen_entity = nullptr;
bool bound_by_later_arg = false;
if (vari_expand && !variadic) {
if (show_error) {
@@ -7378,6 +7390,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
PolyProcData poly_proc_data = {};
if (find_or_generate_polymorphic_procedure_from_parameters(c, entity, &ordered_operands, call, &poly_proc_data)) {
gen_entity = poly_proc_data.gen_entity;
bound_by_later_arg = poly_proc_data.bound_by_later_arg;
Type *gept = base_type(proc_entity_full_type(gen_entity));
GB_ASSERT(is_type_proc(gept));
final_proc_type = proc_entity_full_type(gen_entity);
@@ -7504,6 +7517,7 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
data->score = score;
data->result_type = final_proc_type->Proc.results;
data->gen_entity = gen_entity;
data->bound_by_later_arg = bound_by_later_arg;
Ast *proc_lit = nullptr;
@@ -8076,6 +8090,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
ValidIndexAndScore item = {};
item.score = data.score;
item.bound_by_later_arg = data.bound_by_later_arg;
if (data.gen_entity != nullptr) {
array_add(&proc_entities, data.gen_entity);
@@ -8122,6 +8137,16 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c,
}
c->in_proc_group = false;
isize direct_count = 0;
for (ValidIndexAndScore const &v : valids) {
if (!v.bound_by_later_arg) {
valids[direct_count++] = v;
}
}
if (direct_count > 0) {
valids.count = direct_count;
}
if (max_matched_features > 0) {
for_array(i, valids) {
// NOTE: A polymorphic candidate appends its instantiated entity to proc_entities above,
+88
View File
@@ -2871,6 +2871,94 @@ gb_internal Type *determine_type_from_polymorphic(CheckerContext *ctx, Type *pol
return t_invalid;
}
gb_internal Array<Operand> poly_operands_with_definite_types(CheckerContext *c, TypeProc *pt, Array<Operand> const &operands) {
Array<Operand> result = {};
if (pt->params == nullptr) {
return result;
}
Slice<Entity *> const &params = pt->params->Tuple.variables;
isize n = gb_min(params.count, operands.count);
auto is_untyped_declaring_generic = [&](isize i) -> bool {
Entity *p = params[i];
Operand const &o = operands[i];
if (p->kind != Entity_Variable || p->type->kind != Type_Generic) {
return false;
}
if (!o.deferred_untyped_arg && !(is_operand_value(o) && is_type_untyped(o.type))) {
return false;
}
// NOTE(bill): `$T` is written after the names of the parameters it declares
Entity *te = poly_generic_entity(p->type);
if (te == nullptr || te->token.pos <= p->token.pos) {
return false;
}
for (isize k = 0; k < i; k++) {
if (params[k]->type == p->type) {
return false;
}
}
return true;
};
bool any_untyped = false;
for (isize i = 0; i < n; i++) {
if (is_untyped_declaring_generic(i)) {
any_untyped = true;
break;
}
}
if (!any_untyped) {
return result;
}
PolySubst subst = {};
subst.items.allocator = temporary_allocator();
for (isize i = 0; i < n; i++) {
Entity *p = params[i];
Operand const &o = operands[i];
bool definite = false;
if (p->kind == Entity_TypeName) {
definite = o.mode == Addressing_Type;
} else if (p->kind == Entity_Variable) {
definite = !o.deferred_untyped_arg && is_operand_value(o) && is_type_typed(o.type);
}
if (!definite || !is_type_polymorphic(p->type) || is_type_polymorphic(o.type)) {
continue;
}
PolySubst one = {};
one.items.allocator = temporary_allocator();
if (subst_unify(c, p->type, o.type, &one) == Subst_Matched) {
for (PolyBinding const &b : one.items) {
poly_subst_add(&subst, b.key, b);
}
}
}
for (isize i = 0; i < n; i++) {
if (!is_untyped_declaring_generic(i)) {
continue;
}
PolyBinding *b = poly_subst_find(&subst, poly_generic_entity(params[i]->type));
if (b == nullptr || b->kind != PolyBind_Type) {
continue;
}
if (!operands[i].deferred_untyped_arg && are_types_identical(b->type, default_type(operands[i].type))) {
continue;
}
if (result.data == nullptr) {
result = array_make<Operand>(temporary_allocator(), operands.count);
array_copy(&result, operands, 0);
}
Operand bound = {Addressing_Value};
bound.type = b->type;
bound.expr = operands[i].expr;
result[i] = bound;
}
return result;
}
gb_internal bool is_expr_from_a_parameter(CheckerContext *ctx, Ast *expr) {
if (expr == nullptr) {
return false;
@@ -309,3 +309,20 @@ test_type_inference_on_literals_for_various_types :: proc(t: ^testing.T) {
testing.expect_value(t, group_matrix({}), matrix[2,2]f32{})
}
@test
test_type_inference_on_address_of_literals :: proc(t: ^testing.T) {
S :: struct {x, y: int}
T :: struct {a: f32}
proc_s :: proc(p: ^S) -> int { return p.y }
proc_t :: proc(p: ^T) -> int { return int(p.a) }
proc_string :: proc(s: string) -> int { return -1 }
group :: proc{proc_s, proc_t, proc_string}
testing.expect_value(t, group(&{1, 2}), 2)
testing.expect_value(t, group(&{y = 3}), 3)
testing.expect_value(t, group(&{a = 4}), 4)
poly :: proc(a: $E, p: ^E) -> E { return p^ }
testing.expect_value(t, poly(S{}, &{5, 6}), S{5, 6})
}
}
@@ -55,3 +55,52 @@ test_type_inference_in_min_max_clamp :: proc(t: ^testing.T) {
x: E = min(.B, .C)
testing.expect_value(t, x, E.B)
}
@test
test_type_inference_of_implicit_selectors_against_arrays :: proc(t: ^testing.T) {
E :: enum {A, B, C}
a: [3]E = .B
testing.expect_value(t, a, [3]E{.B, .B, .B})
testing.expect(t, a == .B)
b: [2][2]E = .C
testing.expect_value(t, b[1][0], E.C)
second :: proc(x: [2]E) -> E { return x[1] }
testing.expect_value(t, second(.A), E.A)
}
@test
test_type_inference_of_polymorphic_types_from_later_arguments :: proc(t: ^testing.T) {
E :: enum {A, B, C}
second :: proc(a: $T, b: T) -> T { return b }
e := E.C
testing.expect_value(t, second(.A, e), E.C)
testing.expect_value(t, second(b = e, a = .A), E.C)
x: f32 = 0.5
y := second(1, x)
#assert(type_of(y) == f32)
testing.expect_value(t, y, 0.5)
v := [2]f32{1, 2}
testing.expect_value(t, second({3, 4}, v), v)
p := new(int)
defer free(p)
testing.expect_value(t, second(nil, p), p)
#assert(type_of(second(1, 2.0)) == int)
member_any :: proc(a: int, b: any) -> int { return 1 }
member_poly :: proc(a: $T, b: T) -> int { return 2 }
member_string :: proc(s: string) -> int { return 3 }
group_any :: proc{member_any, member_poly}
group_string :: proc{member_string, member_poly}
f: f64 = 2.5
testing.expect_value(t, group_any(1, f), 1)
testing.expect_value(t, group_string(1, f), 2)
}