refactor(ir): extract CallResolver for call result typing (A3.2 relocation)

Move call-result-type discovery out of Lowering into a new src/ir/calls.zig
(CallResolver): the A3.1 Lowering.inferCallType body moves verbatim into
CallResolver.resultType. inferExprType's `.call` arm now delegates via
callResolver(); Lowering.inferCallType is gone.

CallResolver is a *Lowering facade (Principle 5, like ExprTyper/PackResolver):
call typing reads live lexical-scope / target-type state and the function /
foreign-class / protocol resolver helpers, so it borrows *Lowering. Transform
was `self.` -> `self.l.` plus the file-local static `resolveBuiltin(` ->
`Lowering.resolveBuiltin(`.

Widened to pub only what the facade actually consumes: resolveTypeArg,
inferGenericReturnType, resolveFuncByName, getProtocolInfo,
resolveForeignMethodReturnType, the static resolveBuiltin, and Scope.lookupFn.
resolveTypeArg widening is genuinely required here — the `cast` builtin's
result type calls it.

calls.test.zig adds focused tests (builtin/reflection classification, unknown
callee -> unresolved) for the scope-free paths. Barrel-wired in ir.zig.

This is the relocation half of PLAN-ARCH A3.2; call LOWERING (lowerCall) still
owns its own dispatch, and the CallPlan convergence (one plan shared by typing
and lowering, deleting the duplicated qualified/bare/lazy logic) remains.

Behavior-preserving. Gate: zig build, zig build test (incl. new CallResolver
tests), bash tests/run_examples.sh -> 356/0. lower.zig 18598 -> 18413.
This commit is contained in:
agra
2026-06-02 18:44:08 +03:00
parent 7d069107c8
commit 7f3a7b35ef
4 changed files with 275 additions and 196 deletions

46
src/ir/calls.test.zig Normal file
View File

@@ -0,0 +1,46 @@
// Tests for calls.zig — focused on the call-result-typing paths CallResolver
// owns that need no lexical scope / fn registration: builtin and reflection
// builtin classification, and the unresolved fallthrough. Reached via the
// public `Lowering.inferExprType` delegation.
const std = @import("std");
const ast = @import("../ast.zig");
const Node = ast.Node;
const ir_mod = @import("ir.zig");
const TypeId = ir_mod.TypeId;
const Lowering = ir_mod.Lowering;
fn node(data: ast.Node.Data) Node {
return .{ .span = .{ .start = 0, .end = 0 }, .data = data };
}
test "calls: builtin and reflection result types, unknown fallthrough" {
const alloc = std.testing.allocator;
var module = ir_mod.Module.init(alloc);
defer module.deinit();
var l = Lowering.init(&module);
// One shared throwaway argument — the classified builtins below type by
// callee name and don't inspect it.
var arg = node(.{ .int_literal = .{ .value = 1 } });
var args = [_]*Node{&arg};
const cases = [_]struct { name: []const u8, want: TypeId }{
.{ .name = "size_of", .want = .s64 },
.{ .name = "align_of", .want = .s64 },
.{ .name = "type_name", .want = .string },
.{ .name = "field_count", .want = .s64 },
.{ .name = "is_flags", .want = .bool },
.{ .name = "type_of", .want = .any },
// Unknown bare callee with no builtin / declared fn / scope binding
// types as unresolved, not a fabricated guess.
.{ .name = "definitely_not_a_fn", .want = .unresolved },
};
for (cases) |tc| {
var callee = node(.{ .identifier = .{ .name = tc.name } });
var call = node(.{ .call = .{ .callee = &callee, .args = &args } });
try std.testing.expectEqual(tc.want, l.inferExprType(&call));
}
}