This commit is contained in:
agra
2026-03-02 09:49:43 +02:00
parent f763765ea2
commit ba9c4d69ce
6 changed files with 460 additions and 103 deletions

View File

@@ -1063,7 +1063,12 @@ pub const Lowering = struct {
// Fallback: global variable assignment
if (!handled) {
if (self.global_names.get(id.name)) |gi| {
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = val } }, .void);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != gi.ty and val_ty != .void and gi.ty != .void)
self.coerceToType(val, val_ty, gi.ty)
else
val;
self.builder.emitVoid(.{ .global_set = .{ .global = gi.id, .value = store_val } }, .void);
}
}
},
@@ -1178,7 +1183,20 @@ pub const Lowering = struct {
.deref_expr => |de| {
const ptr = self.lowerExpr(de.operand);
if (asgn.op == .assign) {
self.builder.store(ptr, val);
const pointee_ty = blk: {
const ptr_ty = self.inferExprType(de.operand);
if (!ptr_ty.isBuiltin()) {
const info = self.module.types.get(ptr_ty);
if (info == .pointer) break :blk info.pointer.pointee;
}
break :blk ptr_ty;
};
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != pointee_ty and val_ty != .void and pointee_ty != .void)
self.coerceToType(val, val_ty, pointee_ty)
else
val;
self.builder.store(ptr, store_val);
} else {
const pointee_ty = self.inferExprType(de.operand);
const elem_ty = blk: {
@@ -1266,7 +1284,12 @@ pub const Lowering = struct {
/// Store a value to a GEP, handling both plain and compound assignment.
fn storeOrCompound(self: *Lowering, gep: Ref, val: Ref, op: ast.Assignment.Op, ty: TypeId) void {
if (op == .assign) {
self.builder.store(gep, val);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != ty and val_ty != .void and ty != .void)
self.coerceToType(val, val_ty, ty)
else
val;
self.builder.store(gep, store_val);
} else {
const loaded = self.builder.load(gep, ty);
const result = self.emitCompoundOp(loaded, val, op, ty);
@@ -1433,7 +1456,7 @@ pub const Lowering = struct {
break :blk switch (uop.op) {
.negate => self.builder.emit(.{ .neg = .{ .operand = operand } }, self.inferExprType(uop.operand)),
.not => self.builder.emit(.{ .bool_not = .{ .operand = operand } }, .bool),
.bit_not => self.builder.emit(.{ .bit_not = .{ .operand = operand } }, .s64),
.bit_not => self.builder.emit(.{ .bit_not = .{ .operand = operand } }, self.inferExprType(uop.operand)),
.xx => self.lowerXX(operand, uop.operand),
.address_of => blk2: {
const inner_ty = self.inferExprType(uop.operand);
@@ -1522,6 +1545,33 @@ pub const Lowering = struct {
}
fn lowerBinaryOp(self: *Lowering, bop: *const ast.BinaryOp) Ref {
// Short-circuit: `a and b` → if a then b else false
if (bop.op == .and_op) {
const lhs = self.lowerExpr(bop.lhs);
const rhs_bb = self.freshBlock("and.rhs");
const merge_bb = self.freshBlockWithParams("and.merge", &.{.bool});
const false_val = self.builder.constBool(false);
self.builder.condBr(lhs, rhs_bb, &.{}, merge_bb, &.{false_val});
self.builder.switchToBlock(rhs_bb);
const rhs = self.lowerExpr(bop.rhs);
self.builder.br(merge_bb, &.{rhs});
self.builder.switchToBlock(merge_bb);
return self.builder.blockParam(merge_bb, 0, .bool);
}
// Short-circuit: `a or b` → if a then true else b
if (bop.op == .or_op) {
const lhs = self.lowerExpr(bop.lhs);
const rhs_bb = self.freshBlock("or.rhs");
const merge_bb = self.freshBlockWithParams("or.merge", &.{.bool});
const true_val = self.builder.constBool(true);
self.builder.condBr(lhs, merge_bb, &.{true_val}, rhs_bb, &.{});
self.builder.switchToBlock(rhs_bb);
const rhs = self.lowerExpr(bop.rhs);
self.builder.br(merge_bb, &.{rhs});
self.builder.switchToBlock(merge_bb);
return self.builder.blockParam(merge_bb, 0, .bool);
}
// Special case: optional == null / optional != null
if (bop.op == .eq or bop.op == .neq) {
const lhs_is_null = bop.lhs.data == .null_literal;
@@ -1879,8 +1929,12 @@ pub const Lowering = struct {
const saved_target = self.target_type;
if (is_value and result_type != .void) self.target_type = result_type;
if (is_value) {
const v = self.lowerExpr(ie.then_branch);
var v = self.lowerExpr(ie.then_branch);
if (!self.currentBlockHasTerminator()) {
const v_ty = self.builder.getRefType(v);
if (v_ty != result_type and v_ty != .void and result_type != .void) {
v = self.coerceToType(v, v_ty, result_type);
}
self.builder.br(merge_bb, &.{v});
}
} else {
@@ -1894,8 +1948,12 @@ pub const Lowering = struct {
if (has_else) {
self.builder.switchToBlock(else_bb.?);
if (is_value) {
const v = self.lowerExpr(ie.else_branch.?);
var v = self.lowerExpr(ie.else_branch.?);
if (!self.currentBlockHasTerminator()) {
const v_ty = self.builder.getRefType(v);
if (v_ty != result_type and v_ty != .void and result_type != .void) {
v = self.coerceToType(v, v_ty, result_type);
}
self.builder.br(merge_bb, &.{v});
}
} else {
@@ -3225,9 +3283,37 @@ pub const Lowering = struct {
fn lowerNullCoalesce(self: *Lowering, nc: *const ast.NullCoalesce) Ref {
const lhs = self.lowerExpr(nc.lhs);
const rhs = self.lowerExpr(nc.rhs);
const inner_ty = self.resolveOptionalInner(self.inferExprType(nc.lhs));
return self.builder.emit(.{ .optional_coalesce = .{ .lhs = lhs, .rhs = rhs } }, inner_ty);
// Short-circuit: only evaluate RHS if LHS is null.
// IMPORTANT: optional_unwrap must be in the "has value" branch,
// not before the condBr — the interpreter errors on unwrapping null.
const has_val = self.builder.emit(.{ .optional_has_value = .{ .operand = lhs } }, .bool);
const then_bb = self.freshBlock("nc.has");
const rhs_bb = self.freshBlock("nc.rhs");
const merge_bb = self.freshBlockWithParams("nc.merge", &.{inner_ty});
// If has value, go to then_bb to unwrap; else go to rhs_bb
self.builder.condBr(has_val, then_bb, &.{}, rhs_bb, &.{});
// Then block: unwrap LHS and branch to merge
self.builder.switchToBlock(then_bb);
const unwrapped = self.builder.optionalUnwrap(lhs, inner_ty);
self.builder.br(merge_bb, &.{unwrapped});
// RHS block: evaluate fallback and branch to merge
self.builder.switchToBlock(rhs_bb);
var rhs = self.lowerExpr(nc.rhs);
const rhs_ty = self.builder.getRefType(rhs);
if (rhs_ty != inner_ty and rhs_ty != .void and inner_ty != .void) {
rhs = self.coerceToType(rhs, rhs_ty, inner_ty);
}
self.builder.br(merge_bb, &.{rhs});
// Continue at merge
self.builder.switchToBlock(merge_bb);
return self.builder.blockParam(merge_bb, 0, inner_ty);
}
fn resolveOptionalInner(self: *Lowering, ty: TypeId) TypeId {
@@ -3443,13 +3529,15 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(func_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
// Pack variadic args into a slice if the function has a variadic param
if (self.fn_ast_map.get(func_name)) |fd| {
self.packVariadicCallArgs(fd, c, &args);
}
// Coerce arguments to match parameter types
self.coerceCallArgs(args.items, func.params);
return self.builder.call(fid, args.items, func.ret);
self.coerceCallArgs(args.items, params);
return self.builder.call(fid, args.items, ret_ty);
}
}
// May be a variable holding a function pointer (non-closure)
@@ -3600,11 +3688,13 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(effective_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
if (self.fn_ast_map.get(effective_name)) |fd| {
self.packVariadicCallArgs(fd, c, &args);
}
self.coerceCallArgs(args.items, func.params);
return self.builder.call(fid, args.items, func.ret);
self.coerceCallArgs(args.items, params);
return self.builder.call(fid, args.items, ret_ty);
}
// Check if this is Type.variant(payload) — qualified enum construction
if (ns_name) |type_name| {
@@ -3689,9 +3779,11 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(mangled)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty);
self.coerceCallArgs(method_args.items, func.params);
return self.builder.call(fid, method_args.items, func.ret);
self.coerceCallArgs(method_args.items, params);
return self.builder.call(fid, method_args.items, ret_ty);
}
}
}
@@ -3706,16 +3798,21 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(qualified)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
self.fixupMethodReceiver(&method_args, func, fa.object, obj_ty);
self.coerceCallArgs(method_args.items, func.params);
return self.builder.call(fid, method_args.items, func.ret);
// Note: coerceCallArgs can trigger protocol thunk creation
// (module.addFunction), invalidating func pointer.
// Use pre-extracted params/ret_ty instead of func.* after this.
self.coerceCallArgs(method_args.items, params);
return self.builder.call(fid, method_args.items, ret_ty);
}
}
// Try to resolve as bare function name (method)
if (self.resolveFuncByName(fa.field)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
return self.builder.call(fid, method_args.items, func.ret);
const ret_ty = self.module.functions.items[@intFromEnum(fid)].ret;
return self.builder.call(fid, method_args.items, ret_ty);
}
return self.emitPlaceholder(fa.field);
},
@@ -3739,8 +3836,10 @@ pub const Lowering = struct {
}
if (self.resolveFuncByName(qualified)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
self.coerceCallArgs(args.items, func.params);
return self.builder.call(fid, args.items, func.ret);
const ret_ty = func.ret;
const params = func.params;
self.coerceCallArgs(args.items, params);
return self.builder.call(fid, args.items, ret_ty);
}
}
}
@@ -4014,7 +4113,9 @@ pub const Lowering = struct {
if (ret_ty != .void) {
if (self.lowerBlockValue(lam.body)) |val| {
if (!self.currentBlockHasTerminator()) {
self.builder.ret(val, ret_ty);
const val_ty = self.builder.getRefType(val);
const coerced = if (val_ty != .void) self.coerceToType(val, val_ty, ret_ty) else val;
self.builder.ret(coerced, ret_ty);
}
}
} else {
@@ -4243,6 +4344,33 @@ pub const Lowering = struct {
.deref_expr => |de| {
self.collectCaptures(de.operand, param_names, captures);
},
.for_expr => |fe| {
self.collectCaptures(fe.iterable, param_names, captures);
// Register capture name as local so it's not captured
param_names.put(fe.capture_name, {}) catch {};
self.collectCaptures(fe.body, param_names, captures);
},
.slice_expr => |se| {
self.collectCaptures(se.object, param_names, captures);
if (se.start) |s| self.collectCaptures(s, param_names, captures);
if (se.end) |e| self.collectCaptures(e, param_names, captures);
},
.tuple_literal => |tl| {
for (tl.elements) |elem| {
self.collectCaptures(elem.value, param_names, captures);
}
},
.force_unwrap => |fu| {
self.collectCaptures(fu.operand, param_names, captures);
},
.chained_comparison => |cc| {
for (cc.operands) |op| {
self.collectCaptures(op, param_names, captures);
}
},
.defer_stmt => |ds| {
self.collectCaptures(ds.expr, param_names, captures);
},
else => {},
}
}
@@ -4283,23 +4411,22 @@ pub const Lowering = struct {
fn lowerChainedComparison(self: *Lowering, cc: *const ast.ChainedComparison) Ref {
// a < b < c → (a < b) and (b < c)
// Pre-lower all operands so shared ones (e.g., b) aren't evaluated twice.
if (cc.operands.len < 2 or cc.ops.len == 0) {
return self.builder.constBool(true);
}
var result = self.emitCmp(
self.lowerExpr(cc.operands[0]),
self.lowerExpr(cc.operands[1]),
cc.ops[0],
);
var refs = std.ArrayList(Ref).empty;
defer refs.deinit(self.alloc);
for (cc.operands) |op| {
refs.append(self.alloc, self.lowerExpr(op)) catch unreachable;
}
var result = self.emitCmp(refs.items[0], refs.items[1], cc.ops[0]);
var i: usize = 1;
while (i < cc.ops.len) : (i += 1) {
const next_cmp = self.emitCmp(
self.lowerExpr(cc.operands[i]),
self.lowerExpr(cc.operands[i + 1]),
cc.ops[i],
);
const next_cmp = self.emitCmp(refs.items[i], refs.items[i + 1], cc.ops[i]);
result = self.builder.emit(.{ .bool_and = .{ .lhs = result, .rhs = next_cmp } }, .bool);
}
@@ -4380,7 +4507,12 @@ pub const Lowering = struct {
if (self.scope) |scope| {
if (scope.lookup(id.name)) |binding| {
if (binding.is_alloca) {
self.builder.store(binding.ref, val);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != binding.ty and val_ty != .void and binding.ty != .void)
self.coerceToType(val, val_ty, binding.ty)
else
val;
self.builder.store(binding.ref, store_val);
}
}
}
@@ -4390,16 +4522,21 @@ pub const Lowering = struct {
const obj_ty = self.inferExprType(ie.object);
const elem_ty = self.getElementType(obj_ty);
const ptr_ty = self.module.types.ptrTo(elem_ty);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != elem_ty and val_ty != .void and elem_ty != .void)
self.coerceToType(val, val_ty, elem_ty)
else
val;
// For fixed-size arrays, use the alloca pointer directly
const is_array = !obj_ty.isBuiltin() and self.module.types.get(obj_ty) == .array;
const obj_alloca = if (is_array) self.getExprAlloca(ie.object) else null;
if (obj_alloca) |alloca_ref| {
const gep = self.builder.emit(.{ .index_gep = .{ .lhs = alloca_ref, .rhs = idx } }, ptr_ty);
self.builder.store(gep, val);
self.builder.store(gep, store_val);
} else {
const obj = self.lowerExpr(ie.object);
const gep = self.builder.emit(.{ .index_gep = .{ .lhs = obj, .rhs = idx } }, ptr_ty);
self.builder.store(gep, val);
self.builder.store(gep, store_val);
}
},
.field_access => |fa| {
@@ -4417,11 +4554,29 @@ pub const Lowering = struct {
}
}
const gep = self.builder.structGepTyped(obj_ptr, field_idx, field_ty, obj_ty);
self.builder.store(gep, val);
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != field_ty and val_ty != .void and field_ty != .void)
self.coerceToType(val, val_ty, field_ty)
else
val;
self.builder.store(gep, store_val);
},
.deref_expr => |de| {
const ptr = self.lowerExpr(de.operand);
self.builder.store(ptr, val);
const pointee_ty = blk: {
const ptr_ty = self.inferExprType(de.operand);
if (!ptr_ty.isBuiltin()) {
const info = self.module.types.get(ptr_ty);
if (info == .pointer) break :blk info.pointer.pointee;
}
break :blk ptr_ty;
};
const val_ty = self.builder.getRefType(val);
const store_val = if (val_ty != pointee_ty and val_ty != .void and pointee_ty != .void)
self.coerceToType(val, val_ty, pointee_ty)
else
val;
self.builder.store(ptr, store_val);
},
else => {
_ = self.emitPlaceholder("multi_assign_target");
@@ -4995,6 +5150,8 @@ pub const Lowering = struct {
// Resolve the monomorphized function and call it (stripping type args)
if (self.resolveFuncByName(mangled_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const ret_ty = func.ret;
const params = func.params;
// Build value-only args (skip type param declaration args)
// Use separate index for lowered_args since type params don't consume call args
var value_args = std.ArrayList(Ref).empty;
@@ -5011,8 +5168,8 @@ pub const Lowering = struct {
}
arg_idx += 1;
}
self.coerceCallArgs(value_args.items, func.params);
return self.builder.call(fid, value_args.items, func.ret);
self.coerceCallArgs(value_args.items, params);
return self.builder.call(fid, value_args.items, ret_ty);
}
return self.emitPlaceholder(base_name);
@@ -5209,6 +5366,8 @@ pub const Lowering = struct {
// Build call args (replace cast arg with unboxed value, skip type param decl args)
if (self.resolveFuncByName(mangled_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const callee_ret = func.ret;
const callee_params = func.params;
var call_args = std.ArrayList(Ref).empty;
defer call_args.deinit(self.alloc);
for (fd.params, 0..) |p, pi| {
@@ -5221,8 +5380,8 @@ pub const Lowering = struct {
}
}
}
self.coerceCallArgs(call_args.items, func.params);
const result = self.builder.call(fid, call_args.items, func.ret);
self.coerceCallArgs(call_args.items, callee_params);
const result = self.builder.call(fid, call_args.items, callee_ret);
if (result_slot) |slot| {
self.builder.store(slot, result);
}
@@ -5234,15 +5393,16 @@ pub const Lowering = struct {
self.lazyLowerFunction(resolve_name);
}
if (self.resolveFuncByName(resolve_name)) |fid| {
const func = &self.module.functions.items[@intFromEnum(fid)];
const callee_ret = self.module.functions.items[@intFromEnum(fid)].ret;
const callee_params = self.module.functions.items[@intFromEnum(fid)].params;
var call_args = std.ArrayList(Ref).empty;
defer call_args.deinit(self.alloc);
for (fd.params, 0..) |_, pi| {
if (pi == cast_arg_idx) {
// Coerce unboxed value (typed as ty_id) to param type
var arg = unboxed;
if (pi < func.params.len) {
arg = self.coerceToType(arg, ty_id, func.params[pi].ty);
if (pi < callee_params.len) {
arg = self.coerceToType(arg, ty_id, callee_params[pi].ty);
}
call_args.append(self.alloc, arg) catch unreachable;
} else if (pi < other_args.items.len) {
@@ -5252,12 +5412,12 @@ pub const Lowering = struct {
}
}
// Coerce non-cast args (source type unknown, use s64 default)
for (0..@min(call_args.items.len, func.params.len)) |ci| {
for (0..@min(call_args.items.len, callee_params.len)) |ci| {
if (ci != cast_arg_idx) {
call_args.items[ci] = self.coerceToType(call_args.items[ci], .s64, func.params[ci].ty);
call_args.items[ci] = self.coerceToType(call_args.items[ci], .s64, callee_params[ci].ty);
}
}
const result = self.builder.call(fid, call_args.items, func.ret);
const result = self.builder.call(fid, call_args.items, callee_ret);
if (result_slot) |slot| {
self.builder.store(slot, result);
}
@@ -5363,7 +5523,8 @@ pub const Lowering = struct {
const body_val = self.lowerBlockValue(fd.body);
if (!self.currentBlockHasTerminator()) {
if (body_val) |val| {
const coerced = self.coerceToType(val, .s64, ret_ty);
const val_ty = self.builder.getRefType(val);
const coerced = if (val_ty != .void) self.coerceToType(val, val_ty, ret_ty) else val;
self.builder.ret(coerced, ret_ty);
} else {
self.ensureTerminator(ret_ty);
@@ -7372,26 +7533,25 @@ pub const Lowering = struct {
fn inferGenericReturnType(self: *Lowering, fd: *const ast.FnDecl, c: *const ast.Call) TypeId {
if (fd.return_type == null) return .void;
// Build type bindings from call args (same logic as lowerGenericCall)
// Build ALL type bindings from call args before resolving return type
var tmp_bindings = std.StringHashMap(TypeId).init(self.alloc);
defer tmp_bindings.deinit();
for (fd.type_params) |tp| {
// Strategy 1: direct type param decl ($T: Type) — param.name == tp.name
var found = false;
for (fd.params, 0..) |param, pi| {
if (std.mem.eql(u8, param.name, tp.name)) {
if (pi < c.args.len) {
const ty = self.resolveTypeArg(c.args[pi]);
// Resolve return type with this binding
const saved = self.type_bindings;
var tmp_bindings = std.StringHashMap(TypeId).init(self.alloc);
defer tmp_bindings.deinit();
tmp_bindings.put(tp.name, ty) catch {};
self.type_bindings = tmp_bindings;
const ret = self.resolveTypeWithBindings(fd.return_type.?);
self.type_bindings = saved;
return ret;
}
found = true;
break;
}
}
if (found) continue;
// Strategy 2: inferred from usage (a: $T, b: T) — check ALL matching params, pick widest
var inferred_ty: ?TypeId = null;
for (fd.params, 0..) |param, pi| {
@@ -7414,16 +7574,18 @@ pub const Lowering = struct {
}
}
if (inferred_ty) |ty| {
const saved = self.type_bindings;
var tmp_bindings = std.StringHashMap(TypeId).init(self.alloc);
defer tmp_bindings.deinit();
tmp_bindings.put(tp.name, ty) catch {};
self.type_bindings = tmp_bindings;
const ret = self.resolveTypeWithBindings(fd.return_type.?);
self.type_bindings = saved;
return ret;
}
}
// 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;
}