wasm
This commit is contained in:
288
src/ir/lower.zig
288
src/ir/lower.zig
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user