From ff02184b2e0beebeb5d0bbc3ce86bf78e583d28f Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 2 Oct 2026 16:42:33 +0100 Subject: [PATCH 1/5] Fix #7708: compare a polymorphic record's constant parameters by value in `subst_unify` --- src/check_type.cpp | 5 +++++ tests/issues/run.bat | 2 ++ tests/issues/run.sh | 7 +++++++ 3 files changed, 14 insertions(+) diff --git a/src/check_type.cpp b/src/check_type.cpp index d04fbfe8e..8555140cc 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -2127,6 +2127,11 @@ gb_internal SubstResult subst_unify(CheckerContext *c, Type *pattern, Type *sour if (r != Subst_Matched) { return r; // NoMatch or Unhandled } + // e.g. `M(1, 1, $T)` vs `M(3, 3, f64)` + if (s_e->kind == Entity_Constant && t_e->kind == Entity_Constant && + !compare_exact_values(Token_CmpEq, s_e->Constant.value, t_e->Constant.value)) { + return Subst_NoMatch; + } } } return Subst_Matched; diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 2c2004955..1c68c582d 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -84,6 +84,8 @@ clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b ..\..\..\odin test ..\test_issue_7316.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_7566.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_poly_using_subtype.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_7708.odin %COMMON% || exit /b +..\..\..\odin check ..\test_issue_7708_mismatch.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b ..\..\..\odin test ..\test_issue_7587.odin %COMMON% || exit /b ..\..\..\odin run ..\test_issue_7596.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index fb50f87cd..e8912b635 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -105,6 +105,13 @@ $ODIN run ../test_issue_7564.odin $COMMON $ODIN test ../test_issue_7316.odin $COMMON $ODIN test ../test_issue_7566.odin $COMMON $ODIN test ../test_issue_poly_using_subtype.odin $COMMON +$ODIN test ../test_issue_7708.odin $COMMON +if [[ $($ODIN check ../test_issue_7708_mismatch.odin $COMMON_CHECK 2>&1 >/dev/null | grep -c "Error:") -eq 2 ]]; then + echo "SUCCESSFUL 1/1" +else + echo "SUCCESSFUL 0/1" + exit 1 +fi $ODIN test ../test_issue_7587.odin $COMMON $ODIN run ../test_issue_7596.odin $COMMON $ODIN test ../test_issue_7421.odin $COMMON From f0ac6c3fba70e6e5c8c1f33ea86ebf062c959c85 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 2 Oct 2026 16:55:57 +0100 Subject: [PATCH 2/5] Fix #7700: constant `#soa` field access yields the per-element field array; compare and canonicalize typeid constants --- src/check_expr.cpp | 131 ++++++++++++++++++++++++++++++++++ src/exact_value.cpp | 1 + src/name_canonicalization.cpp | 4 ++ tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + 5 files changed, 138 insertions(+) diff --git a/src/check_expr.cpp b/src/check_expr.cpp index fd01f8cf7..45fa8b7f7 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -5759,6 +5759,134 @@ gb_internal ExactValue get_constant_field_single(CheckerContext *c, ExactValue v +gb_internal Ast *constant_compound_elem_node(AstCompoundLit *cl, i64 index) { + if (cl->elems.count == 0) { + return nullptr; + } + if (cl->elems[0]->kind != Ast_FieldValue) { + return index < cl->elems.count ? cl->elems[index] : nullptr; + } + for (Ast *elem : cl->elems) { + ast_node(fv, FieldValue, elem); + if (is_ast_range(fv->field)) { + ast_node(ie, BinaryExpr, fv->field); + i64 lo = exact_value_to_i64(ie->left->tav.value); + i64 hi = exact_value_to_i64(ie->right->tav.value); + if (ie->op.kind != Token_RangeHalf) { + hi += 1; + } + if (lo <= index && index < hi) { + return fv->value; + } + } else if (fv->field->tav.mode == Addressing_Constant && exact_value_to_i64(fv->field->tav.value) == index) { + return fv->value; + } + } + return nullptr; +} + +gb_internal ExactValue get_constant_soa_field(Type *soa, ExactValue value, Selection sel, bool *success_) { + if (success_) *success_ = false; + + Type *elem = base_type(soa->Struct.soa_elem); + if (sel.index.count != 1 || soa->Struct.soa_kind != StructSoa_Fixed || + value.value_compound->kind != Ast_CompoundLit || + !(elem->kind == Type_Array || (elem->kind == Type_Struct && !elem->Struct.is_raw_union))) { + return empty_exact_value; + } + + i32 field_index = sel.index[0]; + Entity *field = soa->Struct.fields[field_index]; + + Type *field_elem_type = base_type(field->type)->Array.elem; + + isize count = soa->Struct.soa_count; + Ast *soa_node = value.value_compound; + + AstFile *f = soa_node->file(); + ast_node(cl, CompoundLit, soa_node); + + auto nodes = permanent_slice_make(count); + + isize last = -1; + bool has_gaps = false; + + for (isize i = 0; i < count; i++) { + Ast *en = constant_compound_elem_node(cl, i); + if (en == nullptr || en->tav.value.kind == ExactValue_Invalid) { + continue; + } + ExactValue ev = en->tav.value; + if (ev.kind != ExactValue_Compound || ev.value_compound->kind != Ast_CompoundLit) { + return empty_exact_value; + } + ast_node(ecl, CompoundLit, ev.value_compound); + + Ast *fn = nullptr; + if (elem->kind == Type_Struct && ecl->elems.count > 0 && ecl->elems[0]->kind == Ast_FieldValue) { + for (Ast *e : ecl->elems) { + ast_node(fv, FieldValue, e); + if (fv->field->kind == Ast_Ident && fv->field->Ident.token.string == field->token.string) { + fn = fv->value; + break; + } + } + } else { + fn = constant_compound_elem_node(ecl, field_index); + } + if (fn == nullptr) { + continue; + } + + ExactValue fvalue = fn->tav.value; + if (fn->tav.mode == Addressing_Type) { + fvalue = exact_value_typeid(fn->tav.type); + } + if (fvalue.kind == ExactValue_Invalid) { + continue; + } + + Ast *node = alloc_ast_node(nullptr, fn->kind); + gb_memmove(node, fn, ast_node_size(fn->kind)); + node->tav.mode = Addressing_Constant; + node->tav.type = field_elem_type; + node->tav.value = fvalue; + + nodes[i] = node; + has_gaps |= last+1 != i; + last = i; + } + + auto elems = array_make(permanent_allocator(), 0, last+1); + for (isize i = 0; i <= last; i++) { + if (!has_gaps) { + array_add(&elems, nodes[i]); + } else if (nodes[i] != nullptr) { + Token token = {Token_Integer}; + token.string = copy_string(permanent_allocator(), make_string_c(gb_bprintf("%td", i))); + token.pos = ast_token(nodes[i]).pos; + + Ast *index = alloc_ast_node(f, Ast_BasicLit); + index->BasicLit.token = token; + index->tav.mode = Addressing_Constant; + index->tav.type = t_untyped_integer; + index->tav.value = exact_value_i64(i); + + Token eq = {Token_Eq}; + eq.string = str_lit("="); + array_add(&elems, ast_field_value(f, index, nodes[i], eq)); + } + } + + Ast *result = ast_compound_lit(f, nullptr, elems, cl->open, cl->close); + result->tav.mode = Addressing_Constant; + result->tav.type = field->type; + result->tav.value = exact_value_compound(result); + + if (success_) *success_ = true; + return result->tav.value; +} + gb_internal ExactValue get_constant_field(CheckerContext *c, Operand const *operand, Selection sel, bool *success_) { if (operand->mode != Addressing_Constant) { if (success_) *success_ = false; @@ -5777,6 +5905,9 @@ gb_internal ExactValue get_constant_field(CheckerContext *c, Operand const *oper ExactValue value = operand->value; + if (value.kind == ExactValue_Compound && is_type_soa_struct(operand->type)) { + return get_constant_soa_field(base_type(operand->type), value, sel, success_); + } if (value.kind == ExactValue_Compound) { while (sel.index.count > 0) { i32 index = sel.index[0]; diff --git a/src/exact_value.cpp b/src/exact_value.cpp index e1266ff01..b2f855e25 100644 --- a/src/exact_value.cpp +++ b/src/exact_value.cpp @@ -823,6 +823,7 @@ gb_internal i32 exact_value_order(ExactValue const &v) { case ExactValue_Pointer: return 7; case ExactValue_Procedure: + case ExactValue_Typeid: return 8; default: diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 12b1f23cf..46bf5fb31 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -571,6 +571,10 @@ gb_internal void write_canonical_exact_value(TypeWriter *w, ExactValue const &v) return; } } + if (v.kind == ExactValue_Typeid) { + write_type_to_canonical_string(w, v.value_typeid); + return; + } gbString s = exact_value_to_string(v, 1<<16); type_writer_append(w, s, gb_string_length(s)); gb_string_free(s); diff --git a/tests/issues/run.bat b/tests/issues/run.bat index 1c68c582d..a8d9a2b64 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -86,6 +86,7 @@ clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b ..\..\..\odin test ..\test_issue_poly_using_subtype.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_7708.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7708_mismatch.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b +..\..\..\odin test ..\test_issue_7700.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_7587.odin %COMMON% || exit /b ..\..\..\odin run ..\test_issue_7596.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index e8912b635..2d9b70b34 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -112,6 +112,7 @@ else echo "SUCCESSFUL 0/1" exit 1 fi +$ODIN test ../test_issue_7700.odin $COMMON $ODIN test ../test_issue_7587.odin $COMMON $ODIN run ../test_issue_7596.odin $COMMON $ODIN test ../test_issue_7421.odin $COMMON From 1d3f982b50f160d17d4b99c0ea0ed8ccebcfd5bc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 2 Oct 2026 17:10:03 +0100 Subject: [PATCH 3/5] Allow `intrinsics.procedure_of` on procedures without results or requiring a `context`; treat a specialized procedure's type as concrete --- src/check_builtin.cpp | 6 +- src/check_expr.cpp | 8 +-- src/checker.cpp | 4 +- src/checker.hpp | 1 + src/name_canonicalization.cpp | 6 +- src/types.cpp | 11 ++++ tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + tests/issues/test_issue_7700.odin | 55 ++++++++++++++++ tests/issues/test_issue_7708.odin | 66 +++++++++++++++++++ tests/issues/test_issue_7708_mismatch.odin | 14 ++++ .../test_issue_procedure_of_specialized.odin | 55 ++++++++++++++++ 12 files changed, 218 insertions(+), 10 deletions(-) create mode 100644 tests/issues/test_issue_7700.odin create mode 100644 tests/issues/test_issue_7708.odin create mode 100644 tests/issues/test_issue_7708_mismatch.odin create mode 100644 tests/issues/test_issue_procedure_of_specialized.odin diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 701de7a67..8de99553c 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -8518,8 +8518,12 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As { Ast *call_expr = unparen_expr(ce->args[0]); Operand op = {}; + bool prev_in_procedure_of = c->in_procedure_of; + c->in_procedure_of = true; check_expr_base(c, &op, ce->args[0], nullptr); - if (op.mode != Addressing_Value || call_expr == nullptr || call_expr->kind != Ast_CallExpr) { + c->in_procedure_of = prev_in_procedure_of; + bool is_call_mode = op.mode == Addressing_Value || op.mode == Addressing_NoValue || op.mode == Addressing_OptionalOk; + if (!is_call_mode || call_expr == nullptr || call_expr->kind != Ast_CallExpr) { error(ce->args[0], "Expected a call expression for '%.*s'", LIT(builtin_name)); return false; } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 45fa8b7f7..a3bf9704f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -4071,7 +4071,7 @@ gb_internal void check_cast(CheckerContext *c, Operand *x, Type *type, bool forb // identical casts that cannot be foreseen or otherwise // forbidden, so just skip them. if (forbid_identical && check_vet_flags(c) & VetFlag_Cast && - (c->curr_proc_sig == nullptr || !is_type_polymorphic(c->curr_proc_sig))) { + (c->curr_proc_sig == nullptr || !is_type_polymorphic_or_specialized_proc(c->curr_proc_sig))) { Type *src_exact = x->type; Type *dst_exact = type; @@ -4212,7 +4212,7 @@ gb_internal bool check_transmute(CheckerContext *c, Ast *node, Operand *o, Type // identical casts that cannot be foreseen or otherwise // forbidden, so just skip them. if (forbid_identical && check_vet_flags(c) & VetFlag_Cast && - (c->curr_proc_sig == nullptr || !is_type_polymorphic(c->curr_proc_sig)) && + (c->curr_proc_sig == nullptr || !is_type_polymorphic_or_specialized_proc(c->curr_proc_sig)) && check_is_castable_to(c, &src, dst_t)) { if (are_types_identical(src_t, dst_t)) { gbString oper_str = expr_to_string(o->expr); @@ -7977,7 +7977,7 @@ gb_internal CallArgumentData check_call_arguments_proc_group(CheckerContext *c, CheckerContext ctx = *c; ctx.no_polymorphic_errors = true; - ctx.allow_polymorphic_types = is_type_polymorphic(pt); + ctx.allow_polymorphic_types = is_type_polymorphic_or_specialized_proc(pt); ctx.hide_polymorphic_errors = true; bool is_a_candidate = check_call_arguments_single(&ctx, call, operand, @@ -9543,7 +9543,7 @@ gb_internal ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *c } pt = base_type(pt); - if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin) { + if (pt->kind == Type_Proc && pt->Proc.calling_convention == ProcCC_Odin && !c->in_procedure_of) { if ((c->scope->flags & ScopeFlag_ContextDefined) == 0) { ERROR_BLOCK(); if (c->scope->flags & ScopeFlag_File) { diff --git a/src/checker.cpp b/src/checker.cpp index d9e5f208a..5b919d8a8 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -6826,8 +6826,8 @@ gb_internal void check_deferred_procedures(Checker *c) { continue; } - bool src_poly = is_type_polymorphic(src->type); - bool dst_poly = is_type_polymorphic(dst->type); + bool src_poly = is_type_polymorphic_or_specialized_proc(src->type); + bool dst_poly = is_type_polymorphic_or_specialized_proc(dst->type); if (dst_poly && !src_poly) { error(src->token, "A polymorphic deferred procedure '%.*s' requires the initial procedure '%.*s' to be polymorphic as well", LIT(dst->token.string), LIT(src->token.string)); continue; diff --git a/src/checker.hpp b/src/checker.hpp index 300395fb2..6c91c2b91 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -878,6 +878,7 @@ struct CheckerContext { u32 stmt_flags; bool in_enum_type; bool in_proc_group; + bool in_procedure_of; bool allow_polymorphic_types; bool disallow_polymorphic_return_types; // NOTE(zen3ger): no poly type decl in return types bool no_polymorphic_errors; diff --git a/src/name_canonicalization.cpp b/src/name_canonicalization.cpp index 46bf5fb31..c93c4fc8b 100644 --- a/src/name_canonicalization.cpp +++ b/src/name_canonicalization.cpp @@ -658,7 +658,7 @@ 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(p->type)) { + if (is_type_polymorphic_or_specialized_proc(p->type)) { type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR); write_type_to_canonical_string(w, p->type); } @@ -688,7 +688,7 @@ 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(e->type)) { + if (is_type_polymorphic_or_specialized_proc(e->type)) { type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR); write_type_to_canonical_string(w, e->type); } @@ -828,7 +828,7 @@ 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(e->type)) { + if (is_type_polymorphic_or_specialized_proc(e->type)) { type_writer_appendc(w, CANONICAL_TYPE_SEPARATOR); write_type_to_canonical_string(w, e->type); } diff --git a/src/types.cpp b/src/types.cpp index 40e608249..72210bbcf 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -2609,6 +2609,9 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { break; case Type_Proc: + if (t->Proc.is_poly_specialized) { + return or_specialized; + } if (t->Proc.is_polymorphic) { return true; } @@ -2680,6 +2683,14 @@ gb_internal bool is_type_polymorphic(Type *t, bool or_specialized=false) { return false; } +// e.g. to name a procedure after its specialization +gb_internal bool is_type_polymorphic_or_specialized_proc(Type *t) { + if (t != nullptr && t->kind == Type_Proc && t->Proc.is_poly_specialized) { + return true; + } + return is_type_polymorphic(t); +} + gb_internal bool type_has_nil(Type *t) { t = base_type(t); diff --git a/tests/issues/run.bat b/tests/issues/run.bat index a8d9a2b64..4beb41ee6 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -87,6 +87,7 @@ clang -c ..\test_issue_sysv_abi.c -o test_issue_sysv_abi_c.o || exit /b ..\..\..\odin test ..\test_issue_7708.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7708_mismatch.odin %COMMON% 2>&1 | find /c "Error:" | findstr /x "2" || exit /b ..\..\..\odin test ..\test_issue_7700.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_procedure_of_specialized.odin %COMMON% || exit /b ..\..\..\odin test ..\test_issue_7587.odin %COMMON% || exit /b ..\..\..\odin run ..\test_issue_7596.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 2d9b70b34..89ccbc2bd 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -113,6 +113,7 @@ else exit 1 fi $ODIN test ../test_issue_7700.odin $COMMON +$ODIN test ../test_issue_procedure_of_specialized.odin $COMMON $ODIN test ../test_issue_7587.odin $COMMON $ODIN run ../test_issue_7596.odin $COMMON $ODIN test ../test_issue_7421.odin $COMMON diff --git a/tests/issues/test_issue_7700.odin b/tests/issues/test_issue_7700.odin new file mode 100644 index 000000000..aac7dbf5c --- /dev/null +++ b/tests/issues/test_issue_7700.odin @@ -0,0 +1,55 @@ +// Tests issue #7700 https://github.com/odin-lang/Odin/issues/7700 +// A field of a constant `#soa` array is the array of that field of each element +package test_issues + +import "base:intrinsics" +import "core:fmt" +import "core:testing" + +Field :: struct { name: string, type: typeid, n: int } + +F1 :: Field{"x", int, 7} + +STUFF :: #soa[3]Field{{"i", i64, 1}, {"f", f64, 2}, {"p", rawptr, 3}} +NAMED :: #soa[3]Field{{name = "a"}, {type = f32, n = 2}, {name = "c", type = bool}} +GAPS :: #soa[4]Field{1 = {"b", u8, 1}, 3 = {"d", u16, 3}} +RANGES :: #soa[4]Field{0..<2 = {"r", i8, 5}, 3 = F1} +VECS :: #soa[3][2]f32{{1, 2}, {3, 4}, {5, 6}} + +Strings :: struct($strings: [3]string) {} +Types :: struct($types: [3]typeid) {} + +strings_proc :: proc($s: [3]string) -> [3]string { return s } +types_proc :: proc($t: [3]typeid) -> [3]typeid { return t } + +#assert(STUFF.name[1] == "f") +#assert(STUFF.n[2] == 3) +#assert(Types(STUFF.type) == Types([3]typeid{i64, f64, rawptr})) +#assert(Types([3]typeid{i64, f64, rawptr}) != Types([3]typeid{int, int, int})) + +@(test) +test_issue_7700 :: proc(t: ^testing.T) { + testing.expect_value(t, fmt.tprint(typeid_of(Strings(STUFF.name))), `Strings($strings={"i", "f", "p"})`) + testing.expect_value(t, fmt.tprint(typeid_of(Types(STUFF.type))), `Types($types={i64, f64, rawptr})`) + testing.expect_value(t, intrinsics.type_canonical_name(Strings(STUFF.name)), `test_issues::Strings(strings:$${"i","f","p"})`) + testing.expect_value(t, intrinsics.type_canonical_name(Types(STUFF.type)), `test_issues::Types(types:$${i64,f64,rawptr})`) + + testing.expect_value(t, STUFF.name, [3]string{"i", "f", "p"}) + testing.expect_value(t, STUFF.type, [3]typeid{i64, f64, rawptr}) + testing.expect_value(t, STUFF.n, [3]int{1, 2, 3}) + testing.expect_value(t, strings_proc(STUFF.name), [3]string{"i", "f", "p"}) + testing.expect_value(t, types_proc(STUFF.type), [3]typeid{i64, f64, rawptr}) + + testing.expect_value(t, NAMED.name, [3]string{"a", "", "c"}) + testing.expect_value(t, NAMED.type, [3]typeid{nil, f32, bool}) + testing.expect_value(t, NAMED.n, [3]int{0, 2, 0}) + + testing.expect_value(t, GAPS.name, [4]string{"", "b", "", "d"}) + testing.expect_value(t, GAPS.type, [4]typeid{nil, u8, nil, u16}) + + testing.expect_value(t, RANGES.name, [4]string{"r", "r", "", "x"}) + testing.expect_value(t, RANGES.n, [4]int{5, 5, 0, 7}) + + testing.expect_value(t, VECS.x, [3]f32{1, 3, 5}) + testing.expect_value(t, VECS.y, [3]f32{2, 4, 6}) +} diff --git a/tests/issues/test_issue_7708.odin b/tests/issues/test_issue_7708.odin new file mode 100644 index 000000000..e0ce853b6 --- /dev/null +++ b/tests/issues/test_issue_7708.odin @@ -0,0 +1,66 @@ +// Tests issue #7708 https://github.com/odin-lang/Odin/issues/7708 +// A record's concrete constant parameters must match, e.g. `M(1, 1, $T)` does not take a `M(3, 3, f64)` +package test_issues + +import "core:testing" + +M :: struct($R, $C: int, $T: typeid) { data: [C][R]T } + +m11 :: proc(m: M(1, 1, $T)) -> int { return 11 } +m33 :: proc(m: M(3, 3, $T)) -> int { return 33 } +m_group :: proc{m11, m33} + +m_forward :: proc(m: M($R, $C, $T)) -> int { return m_group(m) } + +c11 :: proc(m: $X/M(1, 1, $T)) -> int { return 11 } +c33 :: proc(m: $X/M(3, 3, $T)) -> int { return 33 } +c_group :: proc{c11, c33} + +p11 :: proc(m: ^M(1, 1, $T)) -> int { return 11 } +p33 :: proc(m: ^M(3, 3, $T)) -> int { return 33 } +p_group :: proc{p11, p33} + +Kind :: enum { A, B } +E :: struct($K: Kind, $T: typeid) { x: T } +ea :: proc(e: E(.A, $T)) -> int { return 1 } +eb :: proc(e: E(.B, $T)) -> int { return 2 } +e_group :: proc{ea, eb} + +S :: struct($N: string, $T: typeid) { x: T } +sa :: proc(s: S("a", $T)) -> int { return 1 } +sb :: proc(s: S("b", $T)) -> int { return 2 } +s_group :: proc{sa, sb} + +U :: union($N: int, $T: typeid) { [N]T } +u1 :: proc(u: U(1, $T)) -> int { return 1 } +u2 :: proc(u: U(2, $T)) -> int { return 2 } +u_group :: proc{u1, u2} + +@(test) +test_issue_7708 :: proc(t: ^testing.T) { + a: M(1, 1, i32) + b: M(3, 3, f64) + testing.expect_value(t, m_group(a), 11) + testing.expect_value(t, m_group(b), 33) + testing.expect_value(t, m_forward(a), 11) + testing.expect_value(t, m_forward(b), 33) + testing.expect_value(t, c_group(a), 11) + testing.expect_value(t, c_group(b), 33) + testing.expect_value(t, p_group(&a), 11) + testing.expect_value(t, p_group(&b), 33) + + ea_: E(.A, int) + eb_: E(.B, int) + testing.expect_value(t, e_group(ea_), 1) + testing.expect_value(t, e_group(eb_), 2) + + sa_: S("a", int) + sb_: S("b", int) + testing.expect_value(t, s_group(sa_), 1) + testing.expect_value(t, s_group(sb_), 2) + + u1_: U(1, int) + u2_: U(2, int) + testing.expect_value(t, u_group(u1_), 1) + testing.expect_value(t, u_group(u2_), 2) +} diff --git a/tests/issues/test_issue_7708_mismatch.odin b/tests/issues/test_issue_7708_mismatch.odin new file mode 100644 index 000000000..cc812b3a2 --- /dev/null +++ b/tests/issues/test_issue_7708_mismatch.odin @@ -0,0 +1,14 @@ +// Tests issue #7708 https://github.com/odin-lang/Odin/issues/7708 +// A record argument whose constant parameters differ from the parameter's must be rejected +package test_issues + +M :: struct($R, $C: int, $T: typeid) { data: [C][R]T } + +m11 :: proc(m: M(1, 1, $T)) -> int { return 11 } +c11 :: proc(m: $X/M(1, 1, $T)) -> int { return 11 } + +main :: proc() { + m: M(3, 3, f64) + _ = m11(m) + _ = c11(m) +} diff --git a/tests/issues/test_issue_procedure_of_specialized.odin b/tests/issues/test_issue_procedure_of_specialized.odin new file mode 100644 index 000000000..cdd888f4a --- /dev/null +++ b/tests/issues/test_issue_procedure_of_specialized.odin @@ -0,0 +1,55 @@ +// `intrinsics.procedure_of` of a polymorphic call: procedures without results, any calling convention, +// and the specialized procedure is a concrete value usable through `type_of` +package test_issues + +import "base:intrinsics" +import "core:fmt" +import "core:testing" + +without_return :: proc "contextless" ($i: int) {} +without_return_1 :: intrinsics.procedure_of(without_return(1)) + +with_return :: proc "contextless" ($i: int) -> int { return i } +with_return_1 :: intrinsics.procedure_of(with_return(1)) +with_return_1_type :: type_of(with_return_1) + +contextfull :: proc($i: int) -> int { return i } +global_contextfull_1 :: intrinsics.procedure_of(contextfull(1)) + +double :: proc(x: $T) -> T { return x * 2 } +double_int :: intrinsics.procedure_of(double(int(0))) +double_f32 :: intrinsics.procedure_of(double(f32(0))) + +Holder :: struct { + f: type_of(double_int), + g: [2]type_of(double_f32), +} + +@(test) +test_procedure_of_specialized :: proc(t: ^testing.T) { + main_contextfull_2 :: intrinsics.procedure_of(contextfull(2)) + + without_return_1() + testing.expect_value(t, with_return_1(), 1) + testing.expect_value(t, global_contextfull_1(), 1) + testing.expect_value(t, main_contextfull_2(), 2) + + f: with_return_1_type = with_return_1 + testing.expect_value(t, f(), 1) + + g := double_int + testing.expect_value(t, g(21), 42) + testing.expect(t, type_of(double_int) != type_of(double_f32)) + + h := Holder{double_int, {double_f32, double_f32}} + testing.expect_value(t, h.f(4), 8) + testing.expect_value(t, h.g[1](1.5), 3) + + fs: [dynamic]type_of(double_int) + defer delete(fs) + append(&fs, double_int) + testing.expect_value(t, fs[0](5), 10) + + testing.expect_value(t, fmt.tprint(typeid_of(type_of(double_int))), "proc(int) -> int") + testing.expect(t, fmt.tprint(double_int) != "") +} From 3b8e53a991f201fc7495876521e6ba04f4ac2418 Mon Sep 17 00:00:00 2001 From: Louis Novy <101842021+louisnovy@users.noreply.github.com> Date: Fri, 2 Oct 2026 13:39:33 -0400 Subject: [PATCH 4/5] Handle globals initialized with the address of a non constant literal --- src/llvm_backend_expr.cpp | 4 ++- tests/issues/run.bat | 1 + tests/issues/run.sh | 1 + .../test_issue_global_address_of_literal.odin | 36 +++++++++++++++++++ 4 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 tests/issues/test_issue_global_address_of_literal.odin diff --git a/src/llvm_backend_expr.cpp b/src/llvm_backend_expr.cpp index f7096b795..acb1a8ca0 100644 --- a/src/llvm_backend_expr.cpp +++ b/src/llvm_backend_expr.cpp @@ -4365,7 +4365,9 @@ gb_internal lbValue lb_build_unary_and(lbProcedure *p, Ast *expr) { Type *type = v.type; lbAddr addr = {}; if (p->is_startup) { - addr = lb_add_global_generated_from_procedure(p, type, v); + // NOTE: only a constant can be the global's initializer, any other value is written by the store below + lbValue initializer = LLVMIsConstant(v.value) ? v : lbValue{}; + addr = lb_add_global_generated_from_procedure(p, type, initializer); } else { addr = lb_add_local_generated(p, type, false); } diff --git a/tests/issues/run.bat b/tests/issues/run.bat index d01507e36..812952fbb 100644 --- a/tests/issues/run.bat +++ b/tests/issues/run.bat @@ -44,6 +44,7 @@ set COMMON=-define:ODIN_TEST_FANCY=false -file -vet -strict-style -ignore-unused ..\..\..\odin test ..\test_issue_6951_5214.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_6979.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin test ..\test_issue_7008.odin %COMMON% || exit /b +..\..\..\odin test ..\test_issue_global_address_of_literal.odin %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7012.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin check ..\test_issue_7260.odin -no-entry-point %COMMON% || exit /b ..\..\..\odin test ..\test_issue_bool_to_be_conversion.odin %COMMON% || exit /b diff --git a/tests/issues/run.sh b/tests/issues/run.sh index 4302577d5..95e06f526 100755 --- a/tests/issues/run.sh +++ b/tests/issues/run.sh @@ -97,6 +97,7 @@ fi $ODIN test ../test_issue_6951_5214.odin $COMMON $ODIN check ../test_issue_6979.odin -no-entry-point $COMMON_CHECK $ODIN test ../test_issue_7008.odin $COMMON +$ODIN test ../test_issue_global_address_of_literal.odin $COMMON $ODIN check ../test_issue_7012.odin -no-entry-point $COMMON_CHECK $ODIN build ../test_issue_7037.odin $COMMON -o:none $ODIN test ../test_issue_7477_7506.odin $COMMON diff --git a/tests/issues/test_issue_global_address_of_literal.odin b/tests/issues/test_issue_global_address_of_literal.odin new file mode 100644 index 000000000..041bacaca --- /dev/null +++ b/tests/issues/test_issue_global_address_of_literal.odin @@ -0,0 +1,36 @@ +// A global initialized with the address of a compound literal used to crash the backend +// if the literal's value was not a constant. +package test_issues + +import "core:testing" + +Holder :: struct { target: ^int } +Outer :: struct { inner: ^Inner } +Inner :: struct { value: int } +Type :: struct { variant: union { Array_Type } } +Array_Type :: struct { elem: ^Type } + +initial_value :: proc "contextless" () -> int { return 42 } + +number := 7 + +address_of_global := &Holder{target = &number} // https://github.com/odin-lang/Odin/issues/4337 +nested_literal := &Outer{inner = &Inner{value = 3}} // https://github.com/odin-lang/Odin/issues/5950 +value_from_call := &Inner{value = initial_value()} // https://github.com/odin-lang/Odin/issues/3995 +union_variant := &Type{variant = Array_Type{}} // https://github.com/odin-lang/Odin/issues/7197 +dynamic_array := &[dynamic]int{} // https://github.com/odin-lang/Odin/issues/7640 + +@(test) +global_address_of_literal :: proc(t: ^testing.T) { + testing.expect(t, address_of_global.target == &number) + testing.expect_value(t, address_of_global.target^, 7) + testing.expect_value(t, nested_literal.inner.value, 3) + testing.expect_value(t, value_from_call.value, 42) + + _, is_array := union_variant.variant.(Array_Type) + testing.expect(t, is_array) + + append(dynamic_array, 1) + defer delete(dynamic_array^) + testing.expect_value(t, len(dynamic_array), 1) +} From c628768c5efc54fdfa37e9c99ddcbfc9acf10c90 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 2 Oct 2026 23:11:13 +0100 Subject: [PATCH 5/5] 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) +}