From c628768c5efc54fdfa37e9c99ddcbfc9acf10c90 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 2 Oct 2026 23:11:13 +0100 Subject: [PATCH] Strip constant parameters from specialized procedure types; fix calling a parenthesized polymorphic callee --- src/check_expr.cpp | 83 +++++++++++++++---- src/checker.cpp | 9 +- src/llvm_backend_proc.cpp | 15 +++- src/llvm_backend_stmt.cpp | 12 +-- src/name_canonicalization.cpp | 12 +-- .../issues/test_issue_paren_poly_callee.odin | 23 +++++ 6 files changed, 126 insertions(+), 28 deletions(-) create mode 100644 tests/issues/test_issue_paren_poly_callee.odin diff --git a/src/check_expr.cpp b/src/check_expr.cpp index a3bf9704f..6aedd7d17 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -532,6 +532,42 @@ gb_internal u64 proc_type_identity_hash(Type *t) { return h; } +gb_internal Type *strip_poly_specialized_proc_type(Type *full) { + TypeProc *fp = &full->Proc; + auto vars = array_make(permanent_allocator(), 0, fp->param_count); + i32 variadic_index = -1; + if (fp->params != nullptr) { + for_array(i, fp->params->Tuple.variables) { + Entity *e = fp->params->Tuple.variables[i]; + if (e->kind != Entity_Variable) { + continue; + } + if (fp->variadic && i == fp->variadic_index) { + variadic_index = cast(i32)vars.count; + } + array_add(&vars, e); + } + } + Type *params = nullptr; + if (vars.count > 0) { + params = alloc_type_tuple(); + params->Tuple.variables = slice_from_array(vars); + } + + Type *t = alloc_type_proc(fp->scope, params, vars.count, fp->results, fp->result_count, false, fp->calling_convention); + t->Proc.variadic = fp->variadic; + t->Proc.variadic_index = variadic_index; + t->Proc.require_results = fp->require_results; + t->Proc.c_vararg = fp->c_vararg; + t->Proc.has_named_results = fp->has_named_results; + t->Proc.diverging = fp->diverging; + t->Proc.return_by_pointer = fp->return_by_pointer; + t->Proc.optional_ok = fp->optional_ok; + t->Proc.enable_target_feature = fp->enable_target_feature; + t->Proc.require_target_feature = fp->require_target_feature; + return t; +} + // Reuse an existing generated specialization `other`, scheduling its body if unchecked. // Caller must have released gen_procs->mutex first. gb_internal bool reuse_gen_polymorphic_procedure(Checker *checker, Entity *other, Ast *poly_def_node, PolyProcData *poly_proc_data) { @@ -545,7 +581,7 @@ gb_internal bool reuse_gen_polymorphic_procedure(Checker *checker, Entity *other proc_info->file = other->file; proc_info->token = other->token; proc_info->decl = decl; - proc_info->type = other->type; + proc_info->type = proc_entity_full_type(other); proc_info->body = decl->proc_lit->ProcLit.body; proc_info->tags = other->Procedure.tags; proc_info->generated_from_polymorphic = true; @@ -580,7 +616,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E String name = base_entity->token.string; - Type *src = base_type(base_entity->type); + Type *src = base_type(proc_entity_full_type(base_entity)); Type *dst = nullptr; if (type != nullptr) { dst = base_type(type); @@ -596,7 +632,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) { // NOTE: polymorphic procedure check not idempotent without this if (src->Proc.is_poly_specialized && base_entity->Procedure.generated_from_polymorphic) { - if (are_types_identical(src, dst)) { + if (are_types_identical(src, dst) || are_types_identical(base_type(base_entity->type), dst)) { if (poly_proc_data) { poly_proc_data->gen_entity = base_entity; } @@ -688,7 +724,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E continue; } Entity *other = gen_procs->procs[i]; - Type *pt = base_type(other->type); + Type *pt = base_type(proc_entity_full_type(other)); if (are_types_identical(pt, final_proc_type)) { rw_mutex_shared_unlock(&gen_procs->mutex); // @local-mutex @@ -734,7 +770,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E continue; } Entity *other = gen_procs->procs[i]; - Type *pt = base_type(other->type); + Type *pt = base_type(proc_entity_full_type(other)); if (are_types_identical(pt, final_proc_type)) { rw_mutex_shared_unlock(&gen_procs->mutex); // @local-mutex return reuse_gen_polymorphic_procedure(nctx.checker, other, poly_def_node, poly_proc_data); @@ -750,7 +786,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E rw_mutex_lock(&gen_procs->mutex); // @local-mutex for_array(i, gen_procs->procs) { Entity *other = gen_procs->procs[i]; - if (gen_procs->hashes[i] == final_hash && are_types_identical(base_type(other->type), final_proc_type)) { + if (gen_procs->hashes[i] == final_hash && are_types_identical(base_type(proc_entity_full_type(other)), final_proc_type)) { rw_mutex_unlock(&gen_procs->mutex); // @local-mutex return reuse_gen_polymorphic_procedure(nctx.checker, other, poly_def_node, poly_proc_data); } @@ -795,7 +831,11 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E d->defer_use_checked = false; d->para_poly_original = old_decl->entity; - Entity *entity = alloc_entity_procedure(nullptr, token, final_proc_type, tags); + Type *entity_type = final_proc_type; + if (final_proc_type->Proc.is_poly_specialized) { + entity_type = strip_poly_specialized_proc_type(final_proc_type); + } + Entity *entity = alloc_entity_procedure(nullptr, token, entity_type, tags); entity->state.store(EntityState_Resolved); entity->identifier = ident; @@ -7310,9 +7350,9 @@ 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; - Type *gept = base_type(gen_entity->type); + Type *gept = base_type(proc_entity_full_type(gen_entity)); GB_ASSERT(is_type_proc(gept)); - final_proc_type = gen_entity->type; + final_proc_type = proc_entity_full_type(gen_entity); pt = &gept->Proc; } else { @@ -7656,7 +7696,8 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera bool return_on_failure = show_error_mode == CallArgumentErrorMode::NoErrors; - Ast *ident = operand->expr; + Ast *callee = unparen_expr(operand->expr); + Ast *ident = callee; while (ident->kind == Ast_SelectorExpr) { Ast *s = ident->SelectorExpr.selector; ident = s; @@ -7670,6 +7711,7 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera } GB_ASSERT(proc_type != nullptr); + Type *checked_type = proc_type; proc_type = base_type(proc_type); if (proc_type == t_invalid) { return false; @@ -7683,9 +7725,20 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera Entity *entity_to_use = data->gen_entity != nullptr ? data->gen_entity : e; if (!return_on_failure && entity_to_use != nullptr) { + // a specializing call's arguments include its constant parameters + Type *callee_type = entity_to_use->type; + if (data->gen_entity != nullptr) { + callee_type = proc_entity_full_type(data->gen_entity); + } else if (proc_type->Proc.is_poly_specialized) { + callee_type = checked_type; + } add_entity_use(c, ident, entity_to_use); - update_untyped_expr_type(c, operand->expr, entity_to_use->type, true); - add_type_and_value(c, operand->expr, operand->mode, entity_to_use->type, operand->value); + update_untyped_expr_type(c, operand->expr, callee_type, true); + add_type_and_value(c, operand->expr, operand->mode, callee_type, operand->value); + if (callee != operand->expr) { + // e.g. `(foo)(x)`, as the backend calls through the unparenthesized callee + add_type_and_value(c, callee, operand->mode, callee_type, operand->value); + } } if (data->gen_entity != nullptr) { @@ -7708,7 +7761,7 @@ gb_internal bool check_call_arguments_single(CheckerContext *c, Ast *call, Opera } else { decl->where_clauses_evaluated = true; if (ok && (data->gen_entity->flags & EntityFlag_ProcBodyChecked) == 0) { - check_procedure_later(c->checker, e->file, e->token, decl, e->type, decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); + check_procedure_later(c->checker, e->file, e->token, decl, proc_entity_full_type(e), decl->proc_lit->ProcLit.body, decl->proc_lit->ProcLit.tags); } if (is_type_proc(data->gen_entity->type)) { Type *t = base_type(entity_to_use->type); @@ -8635,9 +8688,11 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, Entity *e = proc_entities[valids[0].index]; GB_ASSERT(e != nullptr); + // an entity generated by this call takes the arguments of its full signature + Type *e_type = valids[0].index >= procs.count ? proc_entity_full_type(e) : e->type; check_call_arguments_single(c, call, operand, - e, e->type, + e, e_type, positional_operands, named_operands, CallArgumentErrorMode::ShowErrors, &data, false); diff --git a/src/checker.cpp b/src/checker.cpp index 5b919d8a8..dea5f0a01 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -1906,6 +1906,13 @@ gb_internal DeclInfo *decl_info_of_entity(Entity *e) { return nullptr; } +gb_internal Type *proc_entity_full_type(Entity *e) { + if (e->kind == Entity_Procedure && e->decl_info != nullptr && e->decl_info->gen_proc_type != nullptr) { + return e->decl_info->gen_proc_type; + } + return e->type; +} + // gb_internal DeclInfo *decl_info_of_ident(Ast *ident) { // return decl_info_of_entity(entity_of_node(ident)); // } @@ -6402,7 +6409,7 @@ gb_internal void check_procedure_later_from_entity(Checker *c, Entity *e, char c pi->file = e->file; pi->token = e->token; pi->decl = e->decl_info; - pi->type = e->type; + pi->type = proc_entity_full_type(e); Ast *pl = e->decl_info->proc_lit; GB_ASSERT(pl != nullptr); diff --git a/src/llvm_backend_proc.cpp b/src/llvm_backend_proc.cpp index 902a43e36..a135904d9 100644 --- a/src/llvm_backend_proc.cpp +++ b/src/llvm_backend_proc.cpp @@ -894,7 +894,7 @@ gb_internal void lb_build_nested_proc(lbProcedure *p, AstProcLit *pd, Entity *e) i32 guid = e->token.pos.offset; if (e->decl_info != nullptr && e->decl_info->para_poly_original != nullptr) { name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%d-%llx", LIT(p->name), LIT(pd_name), guid, - cast(unsigned long long)type_hash_canonical_type(e->type)); + cast(unsigned long long)type_hash_canonical_type(proc_entity_full_type(e))); } else { name_len = gb_snprintf(name_text, name_len, "%.*s" ABI_PKG_NAME_SEPARATOR "%.*s-%d", LIT(p->name), LIT(pd_name), guid); } @@ -1403,7 +1403,12 @@ gb_internal lbValue lb_emit_call(lbProcedure *p, lbValue value, Array c bool by_ptr = false; - auto in_args = args; + auto in_args = array_make(permanent_allocator(), 0, args.count); + for_array(i, args) { + if (i >= pt->Proc.param_count || pt->Proc.params->Tuple.variables[i]->kind == Entity_Variable) { + array_add(&in_args, args[i]); + } + } Array result_as_args = {}; switch (kind) { case DeferredProcedure_none: @@ -5192,6 +5197,12 @@ gb_internal lbValue lb_build_call_expr_internal(lbProcedure *p, Ast *expr, lbVal } } + Type *callee_type = proc_expr->tav.type; + if (value.value != nullptr && callee_type != nullptr && callee_type != value.type && + is_type_proc(callee_type) && base_type(callee_type)->Proc.is_poly_specialized) { + value.type = callee_type; + proc_value_type = callee_type; + } GB_ASSERT(value.value != nullptr || is_objc_call || asm_template != nullptr); Type *proc_type_ = base_type(proc_value_type); diff --git a/src/llvm_backend_stmt.cpp b/src/llvm_backend_stmt.cpp index e8a86ce8b..025de69ad 100644 --- a/src/llvm_backend_stmt.cpp +++ b/src/llvm_backend_stmt.cpp @@ -3,18 +3,20 @@ // NOTE(bill): Orders entities by their canonical type gb_internal i32 lb_entity_type_cmp(Entity *x, Entity *y) { - if (x->type == y->type || x->type == nullptr || y->type == nullptr) { + Type *xt = proc_entity_full_type(x); + Type *yt = proc_entity_full_type(y); + if (xt == yt || xt == nullptr || yt == nullptr) { return 0; } - u64 hx = type_hash_canonical_type(x->type); - u64 hy = type_hash_canonical_type(y->type); + u64 hx = type_hash_canonical_type(xt); + u64 hy = type_hash_canonical_type(yt); if (hx != hy) { return hx < hy ? -1 : +1; } // NOTE(bill): Polymorphic instances share their declaration's token, so this is what tells them apart deterministically TEMPORARY_ALLOCATOR_GUARD(); - return string_compare(type_to_canonical_string(temporary_allocator(), x->type), - type_to_canonical_string(temporary_allocator(), y->type)); + return string_compare(type_to_canonical_string(temporary_allocator(), xt), + type_to_canonical_string(temporary_allocator(), yt)); } gb_internal GB_COMPARE_PROC(lb_polymorphic_instance_cmp) { diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index c93c4fc8b..a8ed42a8d 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -658,9 +658,9 @@ gb_internal void write_canonical_parent_prefix(TypeWriter *w, Entity *e) { Entity *p = e->parent_proc_decl.load(std::memory_order_relaxed)->entity; write_canonical_parent_prefix(w, p); type_writer_append(w, p->token.string.text, p->token.string.len); - if (is_type_polymorphic_or_specialized_proc(p->type)) { + if (is_type_polymorphic_or_specialized_proc(proc_entity_full_type(p))) { type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR); - write_type_to_canonical_string(w, p->type); + write_type_to_canonical_string(w, proc_entity_full_type(p)); } type_writer_appendc(w, CANONICAL_NAME_SEPARATOR); @@ -688,9 +688,9 @@ gb_internal void write_canonical_parent_prefix(TypeWriter *w, Entity *e) { type_writer_append(w, e->token.string.text, e->token.string.len); } - if (is_type_polymorphic_or_specialized_proc(e->type)) { + if (is_type_polymorphic_or_specialized_proc(proc_entity_full_type(e))) { type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR); - write_type_to_canonical_string(w, e->type); + write_type_to_canonical_string(w, proc_entity_full_type(e)); } type_writer_appendc(w, CANONICAL_NAME_SEPARATOR); @@ -828,9 +828,9 @@ write_base_name: case Entity_AsmTemplate: case Entity_Variable: type_writer_append(w, e->token.string.text, e->token.string.len); - if (is_type_polymorphic_or_specialized_proc(e->type)) { + if (is_type_polymorphic_or_specialized_proc(proc_entity_full_type(e))) { type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR); - write_type_to_canonical_string(w, e->type); + write_type_to_canonical_string(w, proc_entity_full_type(e)); } break; diff --git a/tests/issues/test_issue_paren_poly_callee.odin b/tests/issues/test_issue_paren_poly_callee.odin new file mode 100644 index 000000000..e2ef45e5c --- /dev/null +++ b/tests/issues/test_issue_paren_poly_callee.odin @@ -0,0 +1,23 @@ +// Calling a polymorphic procedure through a parenthesized callee, e.g. `(foo)(x)` +package test_issues + +import "core:testing" + +@(private="file") +double :: proc(x: $T) -> T { return x * 2 } +@(private="file") +splat :: proc($N: int, x: $T) -> [N]T { return x } + +@(test) +test_paren_poly_callee :: proc(t: ^testing.T) { + testing.expect_value(t, (double)(int(2)), 4) + testing.expect_value(t, ((double))(f32(1.5)), 3) + testing.expect_value(t, (splat)(2, i32(3)), [2]i32{3, 3}) + + big := (splat)(16, f64(1)) + testing.expect_value(t, big[15], 1) + + s := (make)([]int, 3) + defer delete(s) + testing.expect_value(t, len(s), 3) +}