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,