From 1d3f982b50f160d17d4b99c0ea0ed8ccebcfd5bc Mon Sep 17 00:00:00 2001 From: gingerBill Date: Fri, 2 Oct 2026 17:10:03 +0100 Subject: [PATCH] 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) != "") +}