From b7c6ec24b0f48a3e94c6b2a3588a69ba7a5e3ddc Mon Sep 17 00:00:00 2001 From: agra Date: Thu, 28 May 2026 08:03:22 +0300 Subject: [PATCH] =?UTF-8?q?ffi:=20more=20inferExprType=20silent-default=20?= =?UTF-8?q?holes=20=E2=80=94=20null=5Fcoalesce,=20struct=20const,=20reflec?= =?UTF-8?q?tion=20builtins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three additional arms that previously silently fell through to `.s64`: - `.null_coalesce`: `lhs ?? rhs` now returns the inner type of lhs's optional (when applicable), else the rhs's inferred type. Without this, `print("{}\n", iw ?? 0.0)` for `iw: ?f32` inferred as s64 and the float value got truncated through the pack-mono's Any boxing. - `.field_access` struct constant: `Phys.GRAVITY` (a `Struct.CONST` declaration) now consults `struct_const_map` for the resolved field type. Previously the path hit only `lowerFieldAccess`'s constant-resolution shortcut, not the AST-level `inferExprType`, so pack-fn callers misinferred the const's type as `.s64`. - Reflection builtins (`type_name`, `type_eq`, `has_impl`, `field_count`, `field_index`, `field_name`, `is_flags`, `type_of`, `field_value`): their return types live outside `resolveBuiltin`'s table (they dispatch via `tryLowerReflectionCall` instead). Recognise them directly in the `inferExprType` call arm so pack-fn callers mangle the results with the right tag (.bool for `type_eq` / `has_impl` / `is_flags`, .string for `type_name` / `field_name`, etc). All three holes surfaced while attempting the print/format `..$args` migration; the fixes themselves are general improvements and stand independently. 218/218. --- src/ir/lower.zig | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 2f267f4..6f7e42a 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -11868,6 +11868,20 @@ pub const Lowering = struct { else => .s64, }; } + // Reflection builtins live outside `resolveBuiltin`'s + // table (their lowering goes through + // `tryLowerReflectionCall`, not the `BuiltinId` + // dispatch). Recognize them here so pack-fn callers + // mangle their results with the right tag. + if (std.mem.eql(u8, bare_name, "type_name")) return .string; + if (std.mem.eql(u8, bare_name, "type_eq")) return .bool; + if (std.mem.eql(u8, bare_name, "has_impl")) return .bool; + if (std.mem.eql(u8, bare_name, "field_count")) return .s64; + if (std.mem.eql(u8, bare_name, "field_index")) return .s64; + if (std.mem.eql(u8, bare_name, "field_name")) return .string; + if (std.mem.eql(u8, bare_name, "is_flags")) return .bool; + if (std.mem.eql(u8, bare_name, "type_of")) return .any; + if (std.mem.eql(u8, bare_name, "field_value")) return .any; // Check if it's a generic function — infer return type via type bindings if (self.fn_ast_map.get(name)) |fd| { if (fd.type_params.len > 0) { @@ -12008,6 +12022,17 @@ pub const Lowering = struct { if (ppc.contains(fa.object.data.identifier.name)) return .s64; } } + // Struct constant access: `Struct.CONST` — mirrors the + // lowerFieldAccess intercept (line 3851). Without this, + // `Phys.GRAVITY` (f64) inferred as s64 and pack-fn + // callers boxed the float into the int slot. + if (fa.object.data == .identifier) { + const obj_name = fa.object.data.identifier.name; + const qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ obj_name, fa.field }) catch fa.field; + if (self.struct_const_map.get(qualified)) |info| { + if (info.ty) |t| return t; + } + } // M1.3 — `obj.class` on an Obj-C-class pointer returns Class (*void). if (std.mem.eql(u8, fa.field, "class")) { if (self.isObjcClassPointer(self.inferExprType(fa.object))) { @@ -12172,6 +12197,20 @@ pub const Lowering = struct { return .s64; }, .chained_comparison => .bool, + .null_coalesce => |nc| blk: { + // `opt ?? default` — result is the inner type when lhs is + // optional (the unwrap path's value), else falls back to + // the rhs's type. Without this arm pack-fn callers + // misinferred float-optional coalesces as s64 and the + // pack mono mangled the arg as int — the actual f64 value + // got truncated through Any boxing. + const lhs_ty = self.inferExprType(nc.lhs); + if (!lhs_ty.isBuiltin()) { + const info = self.module.types.get(lhs_ty); + if (info == .optional) break :blk info.optional.child; + } + break :blk self.inferExprType(nc.rhs); + }, // Statements don't produce values .assignment, .var_decl, .const_decl, .fn_decl, .return_stmt, .defer_stmt, .push_stmt, .multi_assign, .destructure_decl,