diff --git a/src/ir/lower.zig b/src/ir/lower.zig index f48603b..f10f064 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -2565,12 +2565,20 @@ pub const Lowering = struct { const null_on_rhs = bop.rhs.data == .null_literal; const null_on_lhs = bop.lhs.data == .null_literal; if (null_on_rhs or null_on_lhs) { - const other_ty = if (null_on_rhs) self.inferExprType(bop.lhs) else self.inferExprType(bop.rhs); - if (other_ty != .void) { + var other_ty = if (null_on_rhs) self.inferExprType(bop.lhs) else self.inferExprType(bop.rhs); + // Lower the non-null side first when its type isn't statically + // inferable, and take the null's type from the lowered value — + // never a guess. + var pre_lowered: ?Ref = null; + if (other_ty == .unresolved) { + pre_lowered = self.lowerExpr(if (null_on_rhs) bop.lhs else bop.rhs); + other_ty = self.builder.getRefType(pre_lowered.?); + } + if (other_ty != .void and other_ty != .unresolved) { const saved_tt = self.target_type; self.target_type = other_ty; - const lv = self.lowerExpr(bop.lhs); - const rv = self.lowerExpr(bop.rhs); + const lv = if (null_on_lhs or pre_lowered == null) self.lowerExpr(bop.lhs) else pre_lowered.?; + const rv = if (null_on_rhs or pre_lowered == null) self.lowerExpr(bop.rhs) else pre_lowered.?; self.target_type = saved_tt; const cmp_op: inst_mod.Op = if (bop.op == .eq) .{ .cmp_eq = .{ .lhs = lv, .rhs = rv } } else .{ .cmp_ne = .{ .lhs = lv, .rhs = rv } }; return self.builder.emit(cmp_op, .bool); @@ -2578,8 +2586,13 @@ pub const Lowering = struct { } } var lhs = self.lowerExpr(bop.lhs); - // Set target_type from LHS so enum literals on RHS resolve correctly - const lhs_ty = self.inferExprType(bop.lhs); + // Set target_type from LHS so enum literals on RHS resolve correctly. + // When the LHS isn't statically inferable (e.g. `#objc_call(...)`), use + // the lowered operand's concrete type rather than a guess. + const lhs_ty = blk: { + const it = self.inferExprType(bop.lhs); + break :blk if (it == .unresolved) self.builder.getRefType(lhs) else it; + }; const saved_tt = self.target_type; if (lhs_ty != .void) { if (!lhs_ty.isBuiltin()) { @@ -2874,11 +2887,16 @@ pub const Lowering = struct { // Infer result type from then branch for value if-exprs // If then_branch is null/void, try else_branch (e.g., `if cond then null else val`) const result_type: TypeId = if (is_value) blk: { - const then_ty = self.inferExprType(ie.then_branch); - if (then_ty == .void and ie.else_branch != null) { - break :blk self.inferExprType(ie.else_branch.?); + var t = self.inferExprType(ie.then_branch); + if ((t == .void or t == .unresolved) and ie.else_branch != null) { + t = self.inferExprType(ie.else_branch.?); } - break :blk then_ty; + // Branch type not statically inferable (e.g. `null` / a bare enum + // literal) — use the contextually expected type rather than a guess. + if (t == .unresolved) { + if (self.target_type) |tt| t = tt; + } + break :blk t; } else .void; const then_bb = self.freshBlock("if.then"); @@ -3446,8 +3464,16 @@ pub const Lowering = struct { // Determine if the match produces a value (has non-void arms) // For type-category matches (inside any_to_string), only produce value when force_block_value // For regular enum/optional matches, always produce value if arms are non-void - const inferred_result = self.inferMatchResultType(me); - const is_value = if (is_type_match) self.force_block_value else (self.force_block_value or inferred_result != .void); + var inferred_result = self.inferMatchResultType(me); + // Arms not statically inferable (bare enum literals etc.): only a + // value-position match (`force_block_value`) needs a concrete result — + // use the contextually expected type. A statement match with non-value + // arms is a side-effect (void); don't let a leaked `target_type` turn + // it into a value match. + if (inferred_result == .unresolved) { + inferred_result = if (self.force_block_value) (self.target_type orelse .unresolved) else .void; + } + const is_value = if (is_type_match) self.force_block_value else (self.force_block_value or (inferred_result != .void and inferred_result != .unresolved)); const result_type: TypeId = if (is_value) inferred_result else .void; const merge_params: []const TypeId = if (is_value and result_type != .void) &.{result_type} else &.{}; const merge_bb = self.freshBlockWithParams("match.merge", merge_params); @@ -8977,8 +9003,16 @@ pub const Lowering = struct { } for (call_node.args[pack_start..]) |a| { if (pack_is_comptime) { - args.append(self.alloc, self.lowerExpr(a)) catch return self.builder.constInt(0, .void); - pack_arg_types.append(self.alloc, self.inferExprType(a)) catch return self.builder.constInt(0, .void); + // A comptime `..$args` arg's intended type follows the + // language default (e.g. an int literal is s64) which + // `inferExprType` encodes; the lowered value may be narrower + // (s32). Prefer inference; fall back to the lowered value's + // type only when inference genuinely can't tell. + const r = self.lowerExpr(a); + args.append(self.alloc, r) catch return self.builder.constInt(0, .void); + const it = self.inferExprType(a); + const ty = if (it == .unresolved) self.builder.getRefType(r) else it; + pack_arg_types.append(self.alloc, ty) catch return self.builder.constInt(0, .void); } else { const r = self.lowerExpr(a); args.append(self.alloc, r) catch return self.builder.constInt(0, .void); @@ -10230,6 +10264,7 @@ pub const Lowering = struct { // If we skip null_literal arms and find a concrete type T, and there // were null arms, the result is ?T (optional). var has_null = false; + var saw_unresolved = false; for (me.arms) |arm| { const last_node = if (arm.body.data == .block) blk: { if (arm.body.data.block.stmts.len > 0) { @@ -10243,14 +10278,21 @@ pub const Lowering = struct { continue; } - // First non-null arm determines the type (same as old behavior) + // First arm with a statically-inferable type determines the result. + // An arm whose type isn't inferable from the AST alone (e.g. a bare + // enum literal) doesn't decide — keep looking; the caller falls back + // to the contextual target type if none of the arms resolve. const arm_ty = self.inferExprType(last_node); + if (arm_ty == .unresolved) { + saw_unresolved = true; + continue; + } if (has_null and arm_ty != .void) { return self.module.types.optionalOf(arm_ty); } return arm_ty; } - return .void; + return if (saw_unresolved) .unresolved else .void; } fn isTypeCategoryMatch(me: *const ast.MatchExpr) bool { @@ -12711,12 +12753,12 @@ pub const Lowering = struct { .unary_op => |uop| switch (uop.op) { .not => .bool, .negate => self.inferExprType(uop.operand), - .xx => self.target_type orelse .s64, + .xx => self.target_type orelse .unresolved, .address_of => blk: { const inner = self.inferExprType(uop.operand); break :blk self.module.types.ptrTo(inner); }, - else => .s64, + else => .unresolved, }, .if_expr => |ie| { // If-else: infer from then branch @@ -12753,8 +12795,8 @@ pub const Lowering = struct { break :blk TypeId.f64; }, .size_of, .align_of => .s64, - .cast => if (c.args.len > 0) self.resolveTypeArg(c.args[0]) else .s64, - else => .s64, + .cast => if (c.args.len > 0) self.resolveTypeArg(c.args[0]) else .unresolved, + else => .unresolved, }; } // Reflection builtins live outside `resolveBuiltin`'s @@ -12781,6 +12823,13 @@ pub const Lowering = struct { if (self.resolveFuncByName(name)) |fid| { return self.module.functions.items[@intFromEnum(fid)].ret; } + // Not lowered yet (lazy lowering): take the return type from + // the declared AST. A void/return-less fn is void — not an + // `.unresolved` guess. + if (self.fn_ast_map.get(name)) |fd| { + if (fd.return_type) |rt| return self.resolveType(rt); + return .void; + } // Check if callee is a local closure variable — extract return type if (self.scope) |scope| { if (scope.lookup(bare_name)) |binding| { @@ -12898,9 +12947,9 @@ pub const Lowering = struct { } } else if (c.callee.data == .enum_literal) { // .Variant(args) — dot-shorthand enum construction - return self.target_type orelse .s64; + return self.target_type orelse .unresolved; } - return .s64; + return .unresolved; }, .field_access => |fa| { // Pack-arity intercept: `.len` is s64. Mirrors @@ -13008,7 +13057,7 @@ pub const Lowering = struct { if (f.name == field_name_id) return if (is_opt_chain) self.optionalOfFlattened(f.ty) else f.ty; } } - return .s64; + return .unresolved; }, .identifier => |id| { if (self.scope) |scope| { @@ -13033,7 +13082,7 @@ pub const Lowering = struct { // builtin primitive) referenced in expression position // is a Type value — IR type `.any`. if (self.isKnownTypeName(id.name)) return .any; - return .s64; + return .unresolved; }, .type_expr => |te| { // type_expr can also be a variable reference (e.g., "s1" matches builtin s1 type) @@ -13045,11 +13094,11 @@ pub const Lowering = struct { // A bare type name in expression position (e.g. `s64`, // `Point`, `*u8`) is a Type value — IR type `.any`. if (self.isKnownTypeName(te.name)) return .any; - return .s64; + return .unresolved; }, .enum_literal => { // Enum literals depend on context — use target_type if available - return self.target_type orelse .s64; + return self.target_type orelse .unresolved; }, .struct_literal => |sl| { if (sl.struct_name) |name| { @@ -13057,7 +13106,7 @@ pub const Lowering = struct { return self.module.types.findByName(name_id) orelse self.module.types.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }); } - return self.target_type orelse .s64; + return self.target_type orelse .unresolved; }, .tuple_literal => |tl| { var field_types = std.ArrayList(TypeId).empty; @@ -13105,7 +13154,7 @@ pub const Lowering = struct { const info = self.module.types.get(ptr_ty); if (info == .pointer) return info.pointer.pointee; } - return .s64; + return .unresolved; }, .chained_comparison => .bool, .null_coalesce => |nc| blk: { @@ -13126,7 +13175,7 @@ pub const Lowering = struct { .assignment, .var_decl, .const_decl, .fn_decl, .return_stmt, .defer_stmt, .push_stmt, .multi_assign, .destructure_decl, => .void, - else => .s64, + else => .unresolved, }; } diff --git a/tests/expected/52-match-optional-arms.txt b/tests/expected/52-match-optional-arms.txt index ff6c111..0241b58 100644 --- a/tests/expected/52-match-optional-arms.txt +++ b/tests/expected/52-match-optional-arms.txt @@ -1,6 +1,6 @@ ok: floating is null -ok: left h=0 -ok: center h=1 -ok: top_right h=2 v=0 +ok: left h=.leading +ok: center h=.center +ok: top_right h=.trailing v=.top rect text