|
|
|
|
@@ -9,6 +9,7 @@ const type_bridge = @import("type_bridge.zig");
|
|
|
|
|
const unescape = @import("../unescape.zig");
|
|
|
|
|
const parser_mod = @import("../parser.zig");
|
|
|
|
|
const interp_mod = @import("interp.zig");
|
|
|
|
|
const errors = @import("../errors.zig");
|
|
|
|
|
|
|
|
|
|
const TypeId = types.TypeId;
|
|
|
|
|
const StringId = types.StringId;
|
|
|
|
|
@@ -106,6 +107,7 @@ pub const Lowering = struct {
|
|
|
|
|
ufcs_alias_map: std.StringHashMap([]const u8) = std.StringHashMap([]const u8).init(std.heap.page_allocator), // UFCS alias name → target function name
|
|
|
|
|
target_config: ?@import("../target.zig").TargetConfig = null, // compilation target (for inline if)
|
|
|
|
|
comptime_constants: std.StringHashMap(ComptimeValue) = std.StringHashMap(ComptimeValue).init(std.heap.page_allocator), // compile-time known constants (e.g. OS, ARCH)
|
|
|
|
|
diagnostics: ?*errors.DiagnosticList = null, // error reporting with source locations
|
|
|
|
|
|
|
|
|
|
pub const ComptimeValue = union(enum) {
|
|
|
|
|
int_val: i64,
|
|
|
|
|
@@ -1287,7 +1289,7 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
_ = self.emitPlaceholder("assignment_target");
|
|
|
|
|
_ = self.emitError("assignment_target", asgn.target.span);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1386,7 +1388,7 @@ pub const Lowering = struct {
|
|
|
|
|
.xor_assign => self.builder.emit(.{ .bit_xor = .{ .lhs = lhs, .rhs = rhs } }, ty),
|
|
|
|
|
.shl_assign => self.builder.emit(.{ .shl = .{ .lhs = lhs, .rhs = rhs } }, ty),
|
|
|
|
|
.shr_assign => self.builder.emit(.{ .shr = .{ .lhs = lhs, .rhs = rhs } }, ty),
|
|
|
|
|
else => self.emitPlaceholder("compound_assign"),
|
|
|
|
|
else => self.emitError("compound_assign", null),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1474,8 +1476,13 @@ pub const Lowering = struct {
|
|
|
|
|
const str = self.builder.constString(sid);
|
|
|
|
|
break :blk self.builder.boxAny(str, .string);
|
|
|
|
|
}
|
|
|
|
|
// Unknown identifier — emit placeholder
|
|
|
|
|
break :blk self.emitPlaceholder(id.name);
|
|
|
|
|
// Type-as-value: known type name used where a value is expected (e.g. cast(s64, val))
|
|
|
|
|
// The type arg is lowered but unused — the caller resolves from AST.
|
|
|
|
|
if (self.isKnownTypeName(id.name)) {
|
|
|
|
|
break :blk self.emitPlaceholder(id.name);
|
|
|
|
|
}
|
|
|
|
|
// Unknown identifier
|
|
|
|
|
break :blk self.emitError(id.name, node.span);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
.binary_op => |bop| self.lowerBinaryOp(&bop),
|
|
|
|
|
@@ -1554,7 +1561,7 @@ pub const Lowering = struct {
|
|
|
|
|
break :blk self.lowerInsertExprValue(ins.expr);
|
|
|
|
|
},
|
|
|
|
|
.tuple_literal => |tl| self.lowerTupleLiteral(&tl),
|
|
|
|
|
.spread_expr => self.emitPlaceholder("spread_expr"),
|
|
|
|
|
.spread_expr => self.emitError("spread_expr", node.span),
|
|
|
|
|
.chained_comparison => |cc| self.lowerChainedComparison(&cc),
|
|
|
|
|
|
|
|
|
|
// Statements that can appear in expression position
|
|
|
|
|
@@ -1603,10 +1610,15 @@ pub const Lowering = struct {
|
|
|
|
|
const str = self.builder.constString(sid);
|
|
|
|
|
break :blk self.builder.boxAny(str, .string);
|
|
|
|
|
}
|
|
|
|
|
break :blk self.emitPlaceholder(te.name);
|
|
|
|
|
// Type expressions are always type names from the parser — they appear
|
|
|
|
|
// in value position when used as args to cast() etc. Silent placeholder.
|
|
|
|
|
if (self.isKnownTypeName(te.name)) {
|
|
|
|
|
break :blk self.emitPlaceholder(te.name);
|
|
|
|
|
}
|
|
|
|
|
break :blk self.emitError(te.name, node.span);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
else => self.emitPlaceholder("unknown_expr"),
|
|
|
|
|
else => self.emitError("unknown_expr", node.span),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1766,7 +1778,7 @@ pub const Lowering = struct {
|
|
|
|
|
.bit_xor => self.builder.emit(.{ .bit_xor = .{ .lhs = lhs, .rhs = rhs } }, ty),
|
|
|
|
|
.shl => self.builder.emit(.{ .shl = .{ .lhs = lhs, .rhs = rhs } }, ty),
|
|
|
|
|
.shr => self.builder.emit(.{ .shr = .{ .lhs = lhs, .rhs = rhs } }, ty),
|
|
|
|
|
.in_op => self.emitPlaceholder("in_op"),
|
|
|
|
|
.in_op => self.emitError("in_op", bop.lhs.span),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1955,7 +1967,12 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Optional binding: `if val := expr { ... }`
|
|
|
|
|
// Clear target_type so the ternary's result type doesn't leak into the condition
|
|
|
|
|
// (e.g., `if x != 0 then 1.0 else 2.0` — the `0` must be s64, not f32)
|
|
|
|
|
const saved_cond_target = self.target_type;
|
|
|
|
|
self.target_type = null;
|
|
|
|
|
const opt_val = self.lowerExpr(ie.condition);
|
|
|
|
|
self.target_type = saved_cond_target;
|
|
|
|
|
const cond = if (ie.binding_name != null) blk: {
|
|
|
|
|
// The condition is an optional — emit has_value check
|
|
|
|
|
break :blk self.builder.emit(.{ .optional_has_value = .{ .operand = opt_val } }, .bool);
|
|
|
|
|
@@ -3552,6 +3569,28 @@ pub const Lowering = struct {
|
|
|
|
|
if (hasComptimeParams(fd)) {
|
|
|
|
|
return self.lowerComptimeCall(fd, c);
|
|
|
|
|
}
|
|
|
|
|
// Early detection of generic function calls — skip arg lowering for type params
|
|
|
|
|
// because lowerGenericCall resolves type params from AST nodes, not lowered refs.
|
|
|
|
|
// Only if the name is NOT shadowed by a local variable (closure, fn ptr, etc.)
|
|
|
|
|
const shadowed = if (self.scope) |scope| scope.lookup(c.callee.data.identifier.name) != null else false;
|
|
|
|
|
if (fd.type_params.len > 0 and !shadowed) {
|
|
|
|
|
// Types are explicit when call args match param count (e.g., are_equal(Point, p1, p2))
|
|
|
|
|
// Types are inferred when call args < param count (e.g., are_equal(p1, p2))
|
|
|
|
|
const types_explicit = c.args.len == fd.params.len;
|
|
|
|
|
var lowered_args = std.ArrayList(Ref).empty;
|
|
|
|
|
defer lowered_args.deinit(self.alloc);
|
|
|
|
|
for (c.args, 0..) |arg, ai| {
|
|
|
|
|
// Skip type param args only when types are passed explicitly
|
|
|
|
|
if (types_explicit and ai < fd.params.len and isTypeParamDecl(&fd.params[ai], fd.type_params)) {
|
|
|
|
|
lowered_args.append(self.alloc, Ref.none) catch unreachable;
|
|
|
|
|
} else {
|
|
|
|
|
const saved_target = self.target_type;
|
|
|
|
|
lowered_args.append(self.alloc, self.lowerExpr(arg)) catch unreachable;
|
|
|
|
|
self.target_type = saved_target;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return self.lowerGenericCall(fd, early_name, c, lowered_args.items);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -3576,6 +3615,11 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (c.args, 0..) |arg, ai| {
|
|
|
|
|
// Skip spread expressions — they'll be handled by packVariadicCallArgs from AST
|
|
|
|
|
if (arg.data == .spread_expr) {
|
|
|
|
|
args.append(self.alloc, Ref.none) catch unreachable;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
const saved_target = self.target_type;
|
|
|
|
|
if (ai < param_types.len) {
|
|
|
|
|
self.target_type = param_types[ai];
|
|
|
|
|
@@ -3778,8 +3822,8 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Unresolved — emit placeholder
|
|
|
|
|
return self.emitPlaceholder(id.name);
|
|
|
|
|
// Unresolved function call
|
|
|
|
|
return self.emitError(id.name, c.callee.span);
|
|
|
|
|
},
|
|
|
|
|
.field_access => |fa| {
|
|
|
|
|
// Pattern-match context.allocator.alloc/dealloc → heap_alloc/heap_free
|
|
|
|
|
@@ -3791,6 +3835,30 @@ pub const Lowering = struct {
|
|
|
|
|
if (inner_call.callee.data == .identifier) {
|
|
|
|
|
const inner_name = inner_call.callee.data.identifier.name;
|
|
|
|
|
const resolved = if (self.scope) |scope| (scope.lookupFn(inner_name) orelse inner_name) else inner_name;
|
|
|
|
|
|
|
|
|
|
// Generic struct static method: Animated(Size).make(...)
|
|
|
|
|
if (self.struct_template_map.getPtr(resolved)) |tmpl| {
|
|
|
|
|
const inst_ty = self.instantiateGenericStruct(tmpl, inner_call.args);
|
|
|
|
|
const inst_name = self.formatTypeName(inst_ty);
|
|
|
|
|
// Look up template method, monomorphize, and call
|
|
|
|
|
if (self.struct_instance_template.get(inst_name)) |tmpl_name| {
|
|
|
|
|
const tmpl_qualified = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ tmpl_name, fa.field }) catch fa.field;
|
|
|
|
|
if (self.fn_ast_map.get(tmpl_qualified)) |fd| {
|
|
|
|
|
if (self.struct_instance_bindings.getPtr(inst_name)) |bindings| {
|
|
|
|
|
const mangled = std.fmt.allocPrint(self.alloc, "{s}.{s}", .{ inst_name, fa.field }) catch fa.field;
|
|
|
|
|
if (!self.lowered_functions.contains(mangled)) {
|
|
|
|
|
self.monomorphizeFunction(fd, mangled, bindings);
|
|
|
|
|
}
|
|
|
|
|
if (self.resolveFuncByName(mangled)) |fid| {
|
|
|
|
|
const func = &self.module.functions.items[@intFromEnum(fid)];
|
|
|
|
|
self.coerceCallArgs(args.items, func.params);
|
|
|
|
|
return self.builder.call(fid, args.items, func.ret);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (self.fn_ast_map.get(resolved)) |fd| {
|
|
|
|
|
if (fd.type_params.len > 0) {
|
|
|
|
|
// Try instantiate as type function
|
|
|
|
|
@@ -3908,7 +3976,7 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return self.emitPlaceholder(func_name);
|
|
|
|
|
return self.emitError(func_name, c.callee.span);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Method call: obj.method(args) → prepend obj (or &obj for *Self receivers)
|
|
|
|
|
@@ -4017,7 +4085,7 @@ pub const Lowering = struct {
|
|
|
|
|
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);
|
|
|
|
|
return self.emitError(fa.field, c.callee.span);
|
|
|
|
|
},
|
|
|
|
|
.enum_literal => |el| {
|
|
|
|
|
const target = self.target_type orelse .s64;
|
|
|
|
|
@@ -4803,7 +4871,7 @@ pub const Lowering = struct {
|
|
|
|
|
self.builder.store(ptr, store_val);
|
|
|
|
|
},
|
|
|
|
|
else => {
|
|
|
|
|
_ = self.emitPlaceholder("multi_assign_target");
|
|
|
|
|
_ = self.emitError("multi_assign_target", target.span);
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -5428,7 +5496,7 @@ pub const Lowering = struct {
|
|
|
|
|
return self.builder.call(fid, value_args.items, ret_ty);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return self.emitPlaceholder(base_name);
|
|
|
|
|
return self.emitError(base_name, call_node.callee.span);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Create a monomorphized instance of a generic function.
|
|
|
|
|
@@ -5479,8 +5547,8 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lower the type tag (runtime value) and Any value BEFORE the switch
|
|
|
|
|
const type_tag = self.lowerExpr(type_tag_node orelse return self.emitPlaceholder("dispatch"));
|
|
|
|
|
const any_val = self.lowerExpr(any_val_node orelse return self.emitPlaceholder("dispatch"));
|
|
|
|
|
const type_tag = self.lowerExpr(type_tag_node orelse return self.emitError("dispatch", call_node.callee.span));
|
|
|
|
|
const any_val = self.lowerExpr(any_val_node orelse return self.emitError("dispatch", call_node.callee.span));
|
|
|
|
|
|
|
|
|
|
// Lower non-cast arguments once (before the switch)
|
|
|
|
|
var other_args = std.ArrayList(?Ref).empty;
|
|
|
|
|
@@ -6945,10 +7013,15 @@ pub const Lowering = struct {
|
|
|
|
|
for (sd.type_params, 0..) |tp, i| {
|
|
|
|
|
tps[i] = .{
|
|
|
|
|
.name = self.alloc.dupe(u8, tp.name) catch return,
|
|
|
|
|
.is_type_param = if (tp.constraint.data == .type_expr)
|
|
|
|
|
std.mem.eql(u8, tp.constraint.data.type_expr.name, "Type")
|
|
|
|
|
else
|
|
|
|
|
false,
|
|
|
|
|
// $T: Type, $T: Lerpable, $T: Type/Eq — all are type params
|
|
|
|
|
// Only value params like $N: u32 are non-type
|
|
|
|
|
.is_type_param = if (tp.constraint.data == .type_expr) blk: {
|
|
|
|
|
const cname = tp.constraint.data.type_expr.name;
|
|
|
|
|
// "Type" or a protocol name → type param
|
|
|
|
|
break :blk std.mem.eql(u8, cname, "Type") or
|
|
|
|
|
self.protocol_decl_map.contains(cname) or
|
|
|
|
|
self.protocol_ast_map.contains(cname);
|
|
|
|
|
} else false,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -7363,7 +7436,20 @@ pub const Lowering = struct {
|
|
|
|
|
var call_args = std.ArrayList(Ref).empty;
|
|
|
|
|
defer call_args.deinit(self.alloc);
|
|
|
|
|
// Pass ctx (ref 0) as first arg (it's the concrete *Type disguised as *void)
|
|
|
|
|
call_args.append(self.alloc, Ref.fromIndex(0)) catch unreachable;
|
|
|
|
|
// If the concrete method expects a value (e.g., f32) not a pointer, load from ctx
|
|
|
|
|
const ctx_ref = Ref.fromIndex(0);
|
|
|
|
|
if (concrete_func.params.len > 0) {
|
|
|
|
|
const first_concrete_ty = concrete_func.params[0].ty;
|
|
|
|
|
const first_info = self.module.types.get(first_concrete_ty);
|
|
|
|
|
if (first_info != .pointer) {
|
|
|
|
|
// Concrete expects value — load from ctx pointer
|
|
|
|
|
call_args.append(self.alloc, self.builder.load(ctx_ref, first_concrete_ty)) catch unreachable;
|
|
|
|
|
} else {
|
|
|
|
|
call_args.append(self.alloc, ctx_ref) catch unreachable;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
call_args.append(self.alloc, ctx_ref) catch unreachable;
|
|
|
|
|
}
|
|
|
|
|
for (method.param_types, 0..) |proto_pty, i| {
|
|
|
|
|
var arg_ref = Ref.fromIndex(@intCast(i + 1));
|
|
|
|
|
// If protocol param is a pointer (Self→*void) but concrete method
|
|
|
|
|
@@ -7380,9 +7466,20 @@ pub const Lowering = struct {
|
|
|
|
|
call_args.append(self.alloc, arg_ref) catch unreachable;
|
|
|
|
|
}
|
|
|
|
|
const owned_args = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
|
|
|
|
const result = self.builder.call(concrete_fid, owned_args, method.ret_type);
|
|
|
|
|
const concrete_ret = concrete_func.ret;
|
|
|
|
|
const result = self.builder.call(concrete_fid, owned_args, concrete_ret);
|
|
|
|
|
if (method.ret_type != .void) {
|
|
|
|
|
self.builder.ret(result, method.ret_type);
|
|
|
|
|
// If protocol returns *void (Self) but concrete returns a value type,
|
|
|
|
|
// box the value: alloca+store and return the pointer
|
|
|
|
|
const ret_info = self.module.types.get(method.ret_type);
|
|
|
|
|
const concrete_ret_info = self.module.types.get(concrete_ret);
|
|
|
|
|
if (ret_info == .pointer and concrete_ret_info != .pointer) {
|
|
|
|
|
const slot = self.builder.alloca(concrete_ret);
|
|
|
|
|
self.builder.store(slot, result);
|
|
|
|
|
self.builder.ret(slot, method.ret_type);
|
|
|
|
|
} else {
|
|
|
|
|
self.builder.ret(result, method.ret_type);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
self.builder.retVoid();
|
|
|
|
|
}
|
|
|
|
|
@@ -7459,7 +7556,7 @@ pub const Lowering = struct {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const mi = method_info orelse return self.emitPlaceholder(method_name);
|
|
|
|
|
const mi = method_info orelse return self.emitError(method_name, null);
|
|
|
|
|
const midx = method_idx orelse 0;
|
|
|
|
|
|
|
|
|
|
// Extract ctx from protocol struct (field 0)
|
|
|
|
|
@@ -7473,7 +7570,7 @@ pub const Lowering = struct {
|
|
|
|
|
} else blk: {
|
|
|
|
|
// Vtable: load vtable struct, extract fn_ptr at method_idx
|
|
|
|
|
const vtable_ptr = self.builder.structGet(receiver, 1, void_ptr);
|
|
|
|
|
const vtable_ty = self.protocol_vtable_type_map.get(proto_info.name) orelse return self.emitPlaceholder("vtable");
|
|
|
|
|
const vtable_ty = self.protocol_vtable_type_map.get(proto_info.name) orelse return self.emitError("vtable", null);
|
|
|
|
|
const vtable = self.builder.emit(.{ .deref = .{ .operand = vtable_ptr } }, vtable_ty);
|
|
|
|
|
break :blk self.builder.structGet(vtable, @intCast(midx), void_ptr);
|
|
|
|
|
};
|
|
|
|
|
@@ -7489,9 +7586,9 @@ pub const Lowering = struct {
|
|
|
|
|
for (args, 0..) |a, i| {
|
|
|
|
|
const expected_ty = if (i < mi.param_types.len) mi.param_types[i] else void_ptr;
|
|
|
|
|
const arg_ty = self.builder.getRefType(a);
|
|
|
|
|
// If protocol method expects *void but we have a struct value (not a pointer), convert to pointer
|
|
|
|
|
// If protocol method expects *void but we have a value (struct or primitive), convert to pointer
|
|
|
|
|
const is_pointer_ty = if (!arg_ty.isBuiltin()) self.module.types.get(arg_ty) == .pointer else false;
|
|
|
|
|
if (expected_ty == void_ptr and arg_ty != void_ptr and !arg_ty.isBuiltin() and !is_pointer_ty) {
|
|
|
|
|
if (expected_ty == void_ptr and arg_ty != void_ptr and !is_pointer_ty) {
|
|
|
|
|
const slot = self.builder.alloca(arg_ty);
|
|
|
|
|
self.builder.store(slot, a);
|
|
|
|
|
call_args.append(self.alloc, slot) catch unreachable;
|
|
|
|
|
@@ -7502,7 +7599,22 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const owned = self.alloc.dupe(Ref, call_args.items) catch unreachable;
|
|
|
|
|
return self.builder.emit(.{ .call_indirect = .{ .callee = fn_ptr, .args = owned } }, mi.ret_type);
|
|
|
|
|
const raw_result = self.builder.emit(.{ .call_indirect = .{ .callee = fn_ptr, .args = owned } }, mi.ret_type);
|
|
|
|
|
|
|
|
|
|
// If protocol method returns *void (Self) and the caller expects a value type,
|
|
|
|
|
// unbox: load the concrete value from the returned pointer
|
|
|
|
|
if (mi.ret_type != .void) {
|
|
|
|
|
const ret_info = self.module.types.get(mi.ret_type);
|
|
|
|
|
if (ret_info == .pointer) {
|
|
|
|
|
if (self.target_type) |target| {
|
|
|
|
|
const target_info = self.module.types.get(target);
|
|
|
|
|
if (target_info != .pointer) {
|
|
|
|
|
return self.builder.load(raw_result, target);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return raw_result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Resolve the concrete type name for protocol erasure.
|
|
|
|
|
@@ -8163,6 +8275,24 @@ pub const Lowering = struct {
|
|
|
|
|
return self.builder.emit(.{ .placeholder = sid }, .s64);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if a name refers to a known type (primitive or registered struct/enum/union).
|
|
|
|
|
/// Used to distinguish type-as-value (silent placeholder) from genuinely unresolved names.
|
|
|
|
|
fn isKnownTypeName(self: *Lowering, name: []const u8) bool {
|
|
|
|
|
if (type_bridge.resolveTypePrimitive(name) != null) return true;
|
|
|
|
|
if (self.type_bindings) |bindings| {
|
|
|
|
|
if (bindings.get(name) != null) return true;
|
|
|
|
|
}
|
|
|
|
|
const name_id = self.module.types.internString(name);
|
|
|
|
|
return self.module.types.findByName(name_id) != null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn emitError(self: *Lowering, name: []const u8, span: ?ast.Span) Ref {
|
|
|
|
|
if (self.diagnostics) |diags| {
|
|
|
|
|
diags.addFmt(.err, span, "unresolved: '{s}'", .{name});
|
|
|
|
|
}
|
|
|
|
|
return self.emitPlaceholder(name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Insert a conversion if src_ty and dst_ty differ.
|
|
|
|
|
/// Handles int widening/narrowing, float widening/narrowing, and int↔float.
|
|
|
|
|
fn coerceToType(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId) Ref {
|
|
|
|
|
@@ -8188,6 +8318,14 @@ pub const Lowering = struct {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// void → Optional: produce null (void is the type of null_literal)
|
|
|
|
|
if (src_ty == .void and !dst_ty.isBuiltin()) {
|
|
|
|
|
const dst_info = self.module.types.get(dst_ty);
|
|
|
|
|
if (dst_info == .optional) {
|
|
|
|
|
return self.builder.constNull(dst_ty);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Concrete → Optional wrapping
|
|
|
|
|
if (!dst_ty.isBuiltin()) {
|
|
|
|
|
const dst_info = self.module.types.get(dst_ty);
|
|
|
|
|
|