refactor(ir): add CallPlan + CallResolver.plan(c); resultType delegates (A3.2 convergence step 2)

Introduce CallPlan — the single classification record for a call: kind (14
variants), return_type, a Target union (builtin/func/named/protocol_method/
foreign_method/constructed/none), variant tag, and the prepends_receiver /
prepends_ctx / expands_defaults properties the selected dispatch implies.

Move call recognition into CallResolver.plan(c) (branch order preserved
exactly) and reimplement resultType(c) as plan(c).return_type — the typing
consumer converges onto the plan first. lowerCall is untouched; routing it
through plan(c) is sub-step 3.

10 plan-object tests assert kind/target/variant + receiver/ctx/default
properties for every pinned call form: builtin/reflection, lazy + resolved
direct fn (incl. default-arg expansion + __sx_ctx prepend), closure /
default-conv vs C-conv fn-pointer, protocol dispatch, struct/UFCS #compiler
method, foreign instance vs static, qualified + dot-shorthand enum
construction, namespace fn, and the unresolved fallthrough.

Widen for the new collaborator only: resolveVariantIndex -> pub (plan resolves
the variant tag); Scope/Binding + init/deinit/put -> pub (so unit tests can
stand up a lexical scope for closure/fn-ptr callees without a full lowering).

zig build, zig build test, and tests/run_examples.sh (357/0) all green; no
behavior change.
This commit is contained in:
agra
2026-06-02 20:15:53 +03:00
parent 297f127821
commit 61f1f2368a
4 changed files with 599 additions and 82 deletions

View File

@@ -48,19 +48,23 @@ fn isExportedEntryName(name: []const u8) bool {
// ── Scope ───────────────────────────────────────────────────────────────
const Binding = struct {
pub const Binding = struct {
ref: Ref,
ty: TypeId,
is_alloca: bool, // true if ref is a pointer that needs load
is_ref_capture: bool = false, // `for xs: (*x)` — `ref` is `*elem`; auto-deref in value positions
};
const Scope = struct {
// `init` / `deinit` / `put` are pub so collaborator unit tests (e.g.
// calls.test.zig) can stand up a lexical scope and exercise the
// scope-dependent call forms (closure / fn-pointer callees) without
// driving a full function lowering.
pub const Scope = struct {
map: std.StringHashMap(Binding),
fn_names: std.StringHashMap([]const u8), // bare name → mangled name for local functions
parent: ?*Scope,
fn init(alloc: Allocator, parent: ?*Scope) Scope {
pub fn init(alloc: Allocator, parent: ?*Scope) Scope {
return .{
.map = std.StringHashMap(Binding).init(alloc),
.fn_names = std.StringHashMap([]const u8).init(alloc),
@@ -68,12 +72,12 @@ const Scope = struct {
};
}
fn deinit(self: *Scope) void {
pub fn deinit(self: *Scope) void {
self.map.deinit();
self.fn_names.deinit();
}
fn put(self: *Scope, name: []const u8, binding: Binding) void {
pub fn put(self: *Scope, name: []const u8, binding: Binding) void {
self.map.put(name, binding) catch unreachable;
}
@@ -5617,7 +5621,7 @@ pub const Lowering = struct {
}
/// Resolve a variant name to its tag index within an enum or union type.
fn resolveVariantIndex(self: *Lowering, ty: TypeId, variant_name: []const u8) u32 {
pub fn resolveVariantIndex(self: *Lowering, ty: TypeId, variant_name: []const u8) u32 {
if (ty.isBuiltin()) return 0;
const info = self.module.types.get(ty);
const name_id = self.module.types.internString(variant_name);