From 432da229a7ddf479f36472745aefec693823e278 Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 28 May 2026 07:38:09 +0300 Subject: [PATCH] ffi: fill inferExprType + inferGenericReturnType silent-default holes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three general fixes to AST-level type inference that previously fell through to `.s64`: - `inferGenericReturnType` resolved the function's return type only when `tmp_bindings` was non-empty; otherwise it bailed to `.s64`, which silently mis-typed pack-fns with non-generic literal return types (e.g. `walk(..$args) -> string`). Always resolve via `resolveTypeWithBindings`, even with empty bindings. - `inferExprType` `binary_op` arm: `.in_op` now returns `.bool` alongside the other comparison/logical ops. Previously the `else` branch returned the LHS type (e.g. `2 in (1,2,3)` → `s64`). - `inferExprType` field-access call arm: when a namespace-qualified call (`pkg.hello()`) hasn't been lowered yet, consult `fn_ast_map` for the qualified name AND the bare field name (matches `lowerCall`'s effective-name resolution order). Without this, cross-module calls returned `.s64`. Surfaces during the still-deferred print/format → `..$args` migration where the pack mono's per-position type tag depends on correct call-arg type inference. The fixes themselves are general improvements that stand independently. 217/217. --- src/ir/lower.zig | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/src/ir/lower.zig b/src/ir/lower.zig index dae0986..a59b9ad 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11813,7 +11813,7 @@ pub const Lowering = struct { .bool_literal => .bool, .null_literal => .void, .binary_op => |bop| switch (bop.op) { - .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op => .bool, + .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op, .in_op => .bool, else => self.inferExprType(bop.lhs), }, .unary_op => |uop| switch (uop.op) { @@ -11965,11 +11965,30 @@ pub const Lowering = struct { const ti = self.module.types.get(ty); if (ti == .tagged_union or ti == .@"enum") return ty; } - // Check for qualified function call + // Check for qualified function call. `resolveFuncByName` + // only finds ALREADY-LOWERED functions; namespace + // imports are typically lowered lazily on demand, so + // a fresh `pkg.hello()` call site may resolve through + // `fn_ast_map` first. Without this, the call's return + // type silently falls through to `.s64` and any + // pack-fn caller (e.g. `print("{}\n", pkg.hello())`) + // mangles the arg as s64, mis-tagging the actual + // string in the Any box. const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tn, cfa.field }) catch cfa.field; if (self.resolveFuncByName(qualified)) |fid| { return self.module.functions.items[@intFromEnum(fid)].ret; } + if (self.fn_ast_map.get(qualified)) |qfd| { + if (qfd.return_type) |rt| return self.resolveType(rt); + return .void; + } + // Namespace aliases sometimes register the function + // under its bare name (matches `lowerCall`'s effective- + // name resolution order). + if (self.fn_ast_map.get(cfa.field)) |bfd| { + if (bfd.return_type) |rt| return self.resolveType(rt); + return .void; + } } } else if (c.callee.data == .enum_literal) { // .Variant(args) — dot-shorthand enum construction @@ -12209,15 +12228,20 @@ pub const Lowering = struct { } } - // Resolve return type with all bindings - if (tmp_bindings.count() > 0) { - const saved = self.type_bindings; - self.type_bindings = tmp_bindings; - const ret = self.resolveTypeWithBindings(fd.return_type.?); - self.type_bindings = saved; - return ret; - } - return .s64; + // Resolve return type with whatever bindings we built. Even an + // empty `tmp_bindings` is a valid input — non-generic literal + // return types (e.g. `walk(..$args) -> string`) still need to + // resolve through `resolveTypeWithBindings`, not fall through + // to the historical `.s64` default. The default silently + // misclassified pack-fn calls whose return type was a fixed + // literal — every consumer (e.g. print's pack-shape mangling) + // inferred `s64` and routed the value through the wrong Any + // tag. + const saved = self.type_bindings; + self.type_bindings = tmp_bindings; + const ret = self.resolveTypeWithBindings(fd.return_type.?); + self.type_bindings = saved; + return ret; } /// Lower the `xx` operator (type coercion).