This commit is contained in:
agra
2026-02-19 01:26:04 +02:00
parent fbf8a62362
commit e0e655cd36
11 changed files with 938 additions and 25 deletions

View File

@@ -196,6 +196,10 @@ pub const CodeGen = struct {
global_mutable_vars: std.StringHashMap(NamedValue),
// Declared return types for non-generic functions (preserves signedness lost by LLVM round-trip)
function_return_types: std.StringHashMap(Type),
// Tuple alloca → type mapping (since tuples are anonymous, we track their types by alloca pointer)
tuple_alloca_types: std.AutoHashMap(usize, Type),
// UFCS alias map: alias name → target function name
ufcs_aliases: std.StringHashMap([]const u8),
// Target configuration (triple, cpu, opt level, lib paths, linker)
target_config: TargetConfig = .{},
// Cached primitive LLVM types (initialized once in init(), avoids repeated FFI calls)
@@ -405,6 +409,8 @@ pub const CodeGen = struct {
.foreign_name_map = std.StringHashMap([]const u8).init(allocator),
.global_mutable_vars = std.StringHashMap(NamedValue).init(allocator),
.function_return_types = std.StringHashMap(Type).init(allocator),
.tuple_alloca_types = std.AutoHashMap(usize, Type).init(allocator),
.ufcs_aliases = std.StringHashMap([]const u8).init(allocator),
.target_config = target_config,
.cached_i1 = c.LLVMInt1TypeInContext(ctx),
.cached_i8 = c.LLVMInt8TypeInContext(ctx),
@@ -576,6 +582,14 @@ pub const CodeGen = struct {
.pointer_type, .many_pointer_type, .function_type => self.ptrType(),
.any_type => self.getAnyStructType(),
.meta_type => self.ptrType(),
.tuple_type => |info| {
const n: c_uint = @intCast(info.field_types.len);
const field_llvm_types = self.allocator.alloc(c.LLVMTypeRef, info.field_types.len) catch unreachable;
for (info.field_types, 0..) |ft, i| {
field_llvm_types[i] = self.typeToLLVM(ft);
}
return c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, n, 0);
},
};
}
@@ -1197,6 +1211,9 @@ pub const CodeGen = struct {
.var_decl => |vd| {
try self.registerGlobalVar(vd);
},
.ufcs_alias => |ua| {
try self.ufcs_aliases.put(ua.name, ua.target);
},
else => {},
}
}
@@ -1476,6 +1493,18 @@ pub const CodeGen = struct {
.return_type = ret_ptr,
} };
}
// Tuple type: (T1, T2) or (T1,)
if (tn.data == .tuple_type_expr) {
const tte = tn.data.tuple_type_expr;
const field_types = self.allocator.alloc(Type, tte.field_types.len) catch return .void_type;
for (tte.field_types, 0..) |ft, i| {
field_types[i] = self.resolveType(ft);
}
return .{ .tuple_type = .{
.field_types = field_types,
.field_names = tte.field_names,
} };
}
// Parameterized type: Vector(N, T) or generic struct instantiation
if (tn.data == .parameterized_type_expr) {
const pte = tn.data.parameterized_type_expr;
@@ -2357,6 +2386,9 @@ pub const CodeGen = struct {
const sname = self.resolveAlias(ret_type.struct_type);
const info = try self.getStructInfo(sname);
return c.LLVMBuildLoad2(self.builder, info.llvm_type, raw_val, "retval");
} else if (ret_type.isTuple()) {
const llvm_ty = self.typeToLLVM(ret_type);
return c.LLVMBuildLoad2(self.builder, llvm_ty, raw_val, "retval");
} else if (ret_type.isUnion()) {
const uname = ret_type.union_type;
const resolved = self.resolveAlias(uname);
@@ -2716,6 +2748,39 @@ pub const CodeGen = struct {
return null;
}
// Tuple-typed variable
if (sx_ty.isTuple()) {
const llvm_ty = self.typeToLLVM(sx_ty);
if (vd.value) |val| {
if (val.data == .undef_literal) {
// Zero-initialized tuple
const alloca = try self.buildNamedAlloca(llvm_ty, vd.name);
self.storeNull(llvm_ty, alloca);
try self.registerVariable(vd.name, alloca, sx_ty);
return null;
}
if (val.data == .tuple_literal) {
// Tuple literal — use its alloca directly
const lit_alloca = try self.genTupleLiteral(val.data.tuple_literal);
try self.registerVariable(vd.name, lit_alloca, sx_ty);
return null;
}
// General expression (e.g., function call returning a tuple, or tuple op)
const result = try self.genExpr(val);
// If the result is already a tuple alloca (from concat/repeat/etc), use it directly
if (self.tuple_alloca_types.contains(@intFromPtr(result))) {
try self.registerVariable(vd.name, result, sx_ty);
return null;
}
// Otherwise it's a loaded struct value (e.g., from function call) — store into an alloca
const alloca = try self.buildNamedAlloca(llvm_ty, vd.name);
_ = c.LLVMBuildStore(self.builder, result, alloca);
try self.registerVariable(vd.name, alloca, sx_ty);
return null;
}
return self.emitErrorFmt("tuple variable '{s}' must be initialized", .{vd.name});
}
// Union-typed variable (tagged enum or C-style union)
if (sx_ty.isUnion()) {
const uname = self.resolveAlias(sx_ty.union_type);
@@ -2977,6 +3042,22 @@ pub const CodeGen = struct {
return null;
}
// Tuple-typed constant: tuple literal returns alloca, use directly
if (sx_ty.isTuple()) {
if (cd.value.data == .tuple_literal) {
const lit_alloca = try self.genTupleLiteral(cd.value.data.tuple_literal);
try self.registerVariable(cd.name, lit_alloca, sx_ty);
return null;
}
// General expression (e.g., function call returning a tuple)
const val = try self.genExpr(cd.value);
const llvm_ty = self.typeToLLVM(sx_ty);
const alloca = try self.buildNamedAlloca(llvm_ty, cd.name);
_ = c.LLVMBuildStore(self.builder, val, alloca);
try self.registerVariable(cd.name, alloca, sx_ty);
return null;
}
// Function pointer typed constant
if (sx_ty.isFunctionType()) {
const llvm_ty = self.ptrType();
@@ -3422,6 +3503,31 @@ pub const CodeGen = struct {
return self.genStringComparison(binop.op, lhs, rhs);
}
// Tuple comparison: element-wise
if (lhs_ty.isTuple() and rhs_ty.isTuple() and
(binop.op == .eq or binop.op == .neq or binop.op == .lt or binop.op == .lte or binop.op == .gt or binop.op == .gte))
{
return self.genTupleComparison(binop.op, binop.lhs, binop.rhs, lhs_ty, rhs_ty);
}
// Tuple concatenation: tuple + tuple
if (lhs_ty.isTuple() and rhs_ty.isTuple() and binop.op == .add) {
return self.genTupleConcat(binop.lhs, binop.rhs, lhs_ty, rhs_ty);
}
// Tuple repetition: tuple * int
if (lhs_ty.isTuple() and rhs_ty.isInt() and binop.op == .mul) {
return self.genTupleRepeat(binop.lhs, binop.rhs, lhs_ty);
}
// Membership: value in tuple
if (binop.op == .in_op) {
if (rhs_ty.isTuple()) {
return self.genTupleMembership(binop.lhs, binop.rhs, rhs_ty);
}
return self.emitError("'in' requires a tuple on the right side");
}
const lhs = try self.genExprAsType(binop.lhs, result_type);
const rhs = try self.genExprAsType(binop.rhs, result_type);
return self.genBinaryOp(binop.op, lhs, rhs, result_type);
@@ -3470,6 +3576,9 @@ pub const CodeGen = struct {
const ctx_name: ?[]const u8 = if (self.current_return_type.isStruct()) self.current_return_type.struct_type else null;
return self.genStructLiteral(sl, ctx_name);
},
.tuple_literal => |tl| {
return self.genTupleLiteral(tl);
},
.array_literal => |al| {
// Typed array/vector/slice literal: Type.[elems]
if (al.type_expr) |te| {
@@ -3539,6 +3648,10 @@ pub const CodeGen = struct {
.const_decl => |cd| {
return self.genConstDecl(cd);
},
.ufcs_alias => |ua| {
try self.ufcs_aliases.put(ua.name, ua.target);
return null;
},
.assignment => |asgn| {
return self.genAssignment(asgn);
},
@@ -4181,6 +4294,60 @@ pub const CodeGen = struct {
return alloca;
}
/// Resolve a field name or numeric index to a tuple field index.
fn resolveTupleFieldIndex(info: Type.TupleTypeInfo, field: []const u8) ?usize {
// Try numeric index first: "0", "1", etc.
if (std.fmt.parseInt(usize, field, 10)) |idx| {
if (idx < info.field_types.len) return idx;
} else |_| {}
// Try named lookup
if (info.field_names) |names| {
for (names, 0..) |name, i| {
if (std.mem.eql(u8, name, field)) return i;
}
}
return null;
}
fn genTupleLiteral(self: *CodeGen, tl: ast.TupleLiteral) anyerror!c.LLVMValueRef {
const n = tl.elements.len;
// Infer types for each element
const field_types = try self.allocator.alloc(Type, n);
const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n);
for (tl.elements, 0..) |elem, i| {
field_types[i] = self.inferType(elem.value);
field_llvm_types[i] = self.typeToLLVM(field_types[i]);
}
// Build anonymous LLVM struct type
const llvm_type = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n), 0);
// Alloca and store each element
const alloca = self.buildEntryBlockAlloca(llvm_type, "tuple");
for (tl.elements, 0..) |elem, i| {
const val = try self.genExprAsType(elem.value, field_types[i]);
self.storeStructField(llvm_type, alloca, @intCast(i), val);
}
// Store the tuple type info for later field access
const field_names = if (tl.elements[0].name != null) blk: {
const names = try self.allocator.alloc([]const u8, n);
for (tl.elements, 0..) |elem, i| {
names[i] = elem.name orelse "";
}
break :blk @as(?[]const []const u8, names);
} else null;
// Register this alloca as having tuple type
const tuple_ty = Type{ .tuple_type = .{
.field_names = field_names,
.field_types = field_types,
} };
try self.tuple_alloca_types.put(@intFromPtr(alloca), tuple_ty);
return alloca;
}
/// Generate an array literal as an alloca with elements stored via GEP.
/// If target_ty is provided, elements are converted to the array's element type.
/// Otherwise, element type is inferred from the first element.
@@ -5270,6 +5437,14 @@ pub const CodeGen = struct {
// GEP to payload area, load as variant type
return self.loadStructField(info.llvm_type, entry.ptr, info.payload_field_index, self.typeToLLVM(variant_ty));
}
if (entry.ty.isTuple()) {
const ti = entry.ty.tuple_type;
const idx = resolveTupleFieldIndex(ti, fa.field) orelse
return self.emitErrorFmt("no field '{s}' in tuple", .{fa.field});
const llvm_ty = self.typeToLLVM(entry.ty);
const field_llvm_ty = self.typeToLLVM(ti.field_types[idx]);
return self.loadStructField(llvm_ty, entry.ptr, @intCast(idx), field_llvm_ty);
}
if (entry.ty.isVector()) {
const vec_val = self.loadTyped(entry.ty, entry.ptr, "vec_load");
return self.genVectorExtract(vec_val, fa.field);
@@ -5334,6 +5509,21 @@ pub const CodeGen = struct {
}
}
}
if (obj_ty.isTuple()) {
const ti = obj_ty.tuple_type;
const idx = resolveTupleFieldIndex(ti, fa.field) orelse
return self.emitErrorFmt("no field '{s}' in tuple", .{fa.field});
const llvm_ty = self.typeToLLVM(obj_ty);
const field_llvm_ty = self.typeToLLVM(ti.field_types[idx]);
// If obj is a tuple literal, genExpr returned an alloca pointer directly
if (fa.object.data == .tuple_literal) {
return self.loadStructField(llvm_ty, obj_val, @intCast(idx), field_llvm_ty);
}
// Otherwise obj_val is a loaded struct value — store into tmp and GEP
const tmp = self.buildEntryBlockAlloca(llvm_ty, "tmp_tuple");
_ = c.LLVMBuildStore(self.builder, obj_val, tmp);
return self.loadStructField(llvm_ty, tmp, @intCast(idx), field_llvm_ty);
}
return self.emitError("field access on non-struct/non-vector expression");
}
@@ -5524,7 +5714,7 @@ pub const CodeGen = struct {
.gte => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOGE, lhs, rhs, "getmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntUGE, lhs, rhs, "getmp") else c.LLVMBuildICmp(b, c.LLVMIntSGE, lhs, rhs, "getmp"),
.bit_and => c.LLVMBuildAnd(b, lhs, rhs, "bandtmp"),
.bit_or => c.LLVMBuildOr(b, lhs, rhs, "bortmp"),
.and_op, .or_op => unreachable,
.and_op, .or_op, .in_op => unreachable,
};
}
@@ -5577,6 +5767,198 @@ pub const CodeGen = struct {
return phi;
}
fn genTupleComparison(self: *CodeGen, op: ast.BinaryOp.Op, lhs_node: *ast.Node, rhs_node: *ast.Node, lhs_ty: Type, rhs_ty: Type) !c.LLVMValueRef {
const lhs_info = lhs_ty.tuple_type;
const rhs_info = rhs_ty.tuple_type;
const n = lhs_info.field_types.len;
if (n != rhs_info.field_types.len) return self.emitError("tuple comparison requires same field count");
const lhs_val = try self.genExpr(lhs_node);
const rhs_val = try self.genExpr(rhs_node);
const lhs_llvm_ty = self.typeToLLVM(lhs_ty);
const rhs_llvm_ty = self.typeToLLVM(rhs_ty);
const i1_ty = self.i1Type();
// Equality: AND-reduce field-wise == comparisons
if (op == .eq or op == .neq) {
var result = c.LLVMConstInt(i1_ty, 1, 0); // start with true
for (0..n) |i| {
const field_ty = lhs_info.field_types[i];
const field_llvm_ty = self.typeToLLVM(field_ty);
const lf = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_ty);
const rf = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_ty);
const cmp = self.genBinaryOp(.eq, lf, rf, field_ty);
result = c.LLVMBuildAnd(self.builder, result, cmp, "tuple_eq_and");
}
if (op == .neq) return c.LLVMBuildNot(self.builder, result, "tuple_neq");
return result;
}
// Lexicographic comparison: chain of basic blocks
// For < : at each field, if lhs.i < rhs.i → true, if lhs.i > rhs.i → false, else continue
// For > : swap the sense
// For <= : same as < but tie → true
// For >= : same as > but tie → true
const is_less = (op == .lt or op == .lte);
const tie_result: u64 = if (op == .lte or op == .gte) 1 else 0;
const merge_bb = self.appendBB("tup.cmp.merge");
const phi_count = 2 * n + 1;
const phi_vals = try self.allocator.alloc(c.LLVMValueRef, phi_count);
const phi_bbs = try self.allocator.alloc(c.LLVMBasicBlockRef, phi_count);
var phi_idx: usize = 0;
for (0..n) |i| {
const field_ty = lhs_info.field_types[i];
const field_llvm_ty = self.typeToLLVM(field_ty);
const lf = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_ty);
const rf = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_ty);
// Check if lhs.i < rhs.i (or > if !is_less)
const less_op: ast.BinaryOp.Op = if (is_less) .lt else .gt;
const cmp_less = self.genBinaryOp(less_op, lf, rf, field_ty);
phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, 1, 0); // true
phi_bbs[phi_idx] = self.getCurrentBlock();
phi_idx += 1;
const next_bb = self.appendBB("tup.cmp.next");
_ = c.LLVMBuildCondBr(self.builder, cmp_less, merge_bb, next_bb);
c.LLVMPositionBuilderAtEnd(self.builder, next_bb);
// Check if lhs.i > rhs.i (or < if !is_less)
const greater_op: ast.BinaryOp.Op = if (is_less) .gt else .lt;
const cmp_greater = self.genBinaryOp(greater_op, lf, rf, field_ty);
phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, 0, 0); // false
phi_bbs[phi_idx] = self.getCurrentBlock();
phi_idx += 1;
const eq_bb = self.appendBB("tup.cmp.eq");
_ = c.LLVMBuildCondBr(self.builder, cmp_greater, merge_bb, eq_bb);
c.LLVMPositionBuilderAtEnd(self.builder, eq_bb);
}
// All fields equal — tie
phi_vals[phi_idx] = c.LLVMConstInt(i1_ty, tie_result, 0);
phi_bbs[phi_idx] = self.getCurrentBlock();
phi_idx += 1;
_ = c.LLVMBuildBr(self.builder, merge_bb);
c.LLVMPositionBuilderAtEnd(self.builder, merge_bb);
const phi = c.LLVMBuildPhi(self.builder, i1_ty, "tup_cmp");
c.LLVMAddIncoming(phi, phi_vals.ptr, @ptrCast(phi_bbs.ptr), @intCast(phi_idx));
return phi;
}
fn genTupleConcat(self: *CodeGen, lhs_node: *ast.Node, rhs_node: *ast.Node, lhs_ty: Type, rhs_ty: Type) !c.LLVMValueRef {
const lhs_info = lhs_ty.tuple_type;
const rhs_info = rhs_ty.tuple_type;
const n_lhs = lhs_info.field_types.len;
const n_rhs = rhs_info.field_types.len;
const n_total = n_lhs + n_rhs;
// Build new tuple type
const field_types = try self.allocator.alloc(Type, n_total);
const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n_total);
for (0..n_lhs) |i| {
field_types[i] = lhs_info.field_types[i];
field_llvm_types[i] = self.typeToLLVM(field_types[i]);
}
for (0..n_rhs) |i| {
field_types[n_lhs + i] = rhs_info.field_types[i];
field_llvm_types[n_lhs + i] = self.typeToLLVM(field_types[n_lhs + i]);
}
const result_llvm_ty = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n_total), 0);
const alloca = self.buildEntryBlockAlloca(result_llvm_ty, "tuple_cat");
// Copy fields from lhs
const lhs_val = try self.genExpr(lhs_node);
const lhs_llvm_ty = self.typeToLLVM(lhs_ty);
for (0..n_lhs) |i| {
const fv = self.loadStructField(lhs_llvm_ty, lhs_val, @intCast(i), field_llvm_types[i]);
self.storeStructField(result_llvm_ty, alloca, @intCast(i), fv);
}
// Copy fields from rhs
const rhs_val = try self.genExpr(rhs_node);
const rhs_llvm_ty = self.typeToLLVM(rhs_ty);
for (0..n_rhs) |i| {
const fv = self.loadStructField(rhs_llvm_ty, rhs_val, @intCast(i), field_llvm_types[n_lhs + i]);
self.storeStructField(result_llvm_ty, alloca, @intCast(n_lhs + i), fv);
}
// Register tuple type
const result_ty = Type{ .tuple_type = .{ .field_types = field_types, .field_names = null } };
try self.tuple_alloca_types.put(@intFromPtr(alloca), result_ty);
return alloca;
}
fn genTupleRepeat(self: *CodeGen, tuple_node: *ast.Node, count_node: *ast.Node, tuple_ty: Type) !c.LLVMValueRef {
// Count must be a comptime int literal
const count: usize = switch (count_node.data) {
.int_literal => |il| @intCast(il.value),
else => return self.emitError("tuple repetition count must be a compile-time integer literal"),
};
if (count == 0) return self.emitError("tuple repetition count must be positive");
const info = tuple_ty.tuple_type;
const n_fields = info.field_types.len;
const n_total = n_fields * count;
// Build new tuple type
const field_types = try self.allocator.alloc(Type, n_total);
const field_llvm_types = try self.allocator.alloc(c.LLVMTypeRef, n_total);
for (0..count) |r| {
for (0..n_fields) |f| {
field_types[r * n_fields + f] = info.field_types[f];
field_llvm_types[r * n_fields + f] = self.typeToLLVM(info.field_types[f]);
}
}
const result_llvm_ty = c.LLVMStructTypeInContext(self.context, field_llvm_types.ptr, @intCast(n_total), 0);
const alloca = self.buildEntryBlockAlloca(result_llvm_ty, "tuple_rep");
// Generate tuple value once
const tuple_val = try self.genExpr(tuple_node);
const tuple_llvm_ty = self.typeToLLVM(tuple_ty);
// Copy fields for each repetition
for (0..count) |r| {
for (0..n_fields) |f| {
const fv = self.loadStructField(tuple_llvm_ty, tuple_val, @intCast(f), field_llvm_types[r * n_fields + f]);
self.storeStructField(result_llvm_ty, alloca, @intCast(r * n_fields + f), fv);
}
}
const result_ty = Type{ .tuple_type = .{ .field_types = field_types, .field_names = null } };
try self.tuple_alloca_types.put(@intFromPtr(alloca), result_ty);
return alloca;
}
fn genTupleMembership(self: *CodeGen, value_node: *ast.Node, tuple_node: *ast.Node, tuple_ty: Type) !c.LLVMValueRef {
const info = tuple_ty.tuple_type;
const n = info.field_types.len;
if (n == 0) return c.LLVMConstInt(self.i1Type(), 0, 0);
const value = try self.genExpr(value_node);
const tuple_val = try self.genExpr(tuple_node);
const tuple_llvm_ty = self.typeToLLVM(tuple_ty);
const value_ty = self.inferType(value_node);
const i1_ty = self.i1Type();
// OR-reduce: (field0 == val) OR (field1 == val) OR ...
var result = c.LLVMConstInt(i1_ty, 0, 0); // start with false
for (0..n) |i| {
const field_ty = info.field_types[i];
const field_llvm_ty = self.typeToLLVM(field_ty);
const fv = self.loadStructField(tuple_llvm_ty, tuple_val, @intCast(i), field_llvm_ty);
const common_ty = Type.widen(value_ty, field_ty);
const cmp = self.genBinaryOp(.eq, value, fv, common_ty);
result = c.LLVMBuildOr(self.builder, result, cmp, "tuple_in_or");
}
return result;
}
fn genShortCircuitOp(self: *CodeGen, binop: ast.BinaryOp, is_and: bool) !c.LLVMValueRef {
const lhs_val = self.valueToBool(try self.genExpr(binop.lhs));
const lhs_bb = self.getCurrentBlock();
@@ -5681,16 +6063,46 @@ pub const CodeGen = struct {
// UFCS: obj.method(args...) → method(obj, args...)
const method_name = fa.field;
const method_z = self.allocator.dupeZ(u8, method_name) catch method_name;
if (self.generic_templates.contains(method_name) or
const resolved_method = self.ufcs_aliases.get(method_name) orelse method_name;
const method_z = self.allocator.dupeZ(u8, resolved_method) catch resolved_method;
if (self.generic_templates.contains(resolved_method) or
c.LLVMGetNamedFunction(self.module, method_z.ptr) != null)
{
// Check if receiver is a tuple — if so, splat its elements as leading args
const receiver_type = self.inferType(fa.object);
const is_tuple = receiver_type == .tuple_type;
if (is_tuple) {
const tuple_info = receiver_type.tuple_type;
const n_tuple = tuple_info.field_types.len;
var ufcs_args = try self.allocator.alloc(*Node, n_tuple + call_node.args.len);
// Create synthetic field_access nodes for each tuple element
for (0..n_tuple) |i| {
const syn_node = try self.allocator.create(ast.Node);
var idx_buf: [20]u8 = undefined;
const idx_str = std.fmt.bufPrint(&idx_buf, "{d}", .{i}) catch "0";
const field_name = try self.allocator.dupe(u8, idx_str);
syn_node.* = .{
.span = fa.object.span,
.data = .{ .field_access = .{ .object = fa.object, .field = field_name } },
};
ufcs_args[i] = syn_node;
}
for (call_node.args, 0..) |arg, i| {
ufcs_args[n_tuple + i] = arg;
}
return self.genCallByName(resolved_method, .{
.callee = call_node.callee,
.args = ufcs_args,
});
}
var ufcs_args = try self.allocator.alloc(*Node, call_node.args.len + 1);
ufcs_args[0] = fa.object;
for (call_node.args, 0..) |arg, i| {
ufcs_args[i + 1] = arg;
}
return self.genCallByName(method_name, .{
return self.genCallByName(resolved_method, .{
.callee = call_node.callee,
.args = ufcs_args,
});
@@ -5704,6 +6116,11 @@ pub const CodeGen = struct {
}
fn genCallByName(self: *CodeGen, callee_name: []const u8, call_node: ast.Call) !c.LLVMValueRef {
// Resolve UFCS alias: add :: ufcs num_add; → redirect "add" to "num_add"
if (self.ufcs_aliases.get(callee_name)) |resolved| {
return self.genCallByName(resolved, call_node);
}
// Check if this is a generic function call
if (self.generic_templates.get(callee_name)) |template| {
return self.genGenericCall(callee_name, template, call_node);
@@ -7424,10 +7841,36 @@ pub const CodeGen = struct {
.comptime_expr => |ct| self.inferType(ct.expr),
.binary_op => |binop| {
switch (binop.op) {
.eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op => return .boolean,
.eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op, .in_op => return .boolean,
else => {
const lhs_ty = self.inferType(binop.lhs);
const rhs_ty = self.inferType(binop.rhs);
// Tuple concatenation: (A, B) + (C, D) → (A, B, C, D)
if (lhs_ty.isTuple() and rhs_ty.isTuple() and binop.op == .add) {
const li = lhs_ty.tuple_type;
const ri = rhs_ty.tuple_type;
const n = li.field_types.len + ri.field_types.len;
const ft = self.allocator.alloc(Type, n) catch return .void_type;
for (0..li.field_types.len) |i| ft[i] = li.field_types[i];
for (0..ri.field_types.len) |i| ft[li.field_types.len + i] = ri.field_types[i];
return .{ .tuple_type = .{ .field_types = ft, .field_names = null } };
}
// Tuple repetition: (A, B) * 3 → (A, B, A, B, A, B)
if (lhs_ty.isTuple() and rhs_ty.isInt() and binop.op == .mul) {
const li = lhs_ty.tuple_type;
const count: usize = switch (binop.rhs.data) {
.int_literal => |il| @intCast(il.value),
else => return .void_type,
};
const n = li.field_types.len * count;
const ft = self.allocator.alloc(Type, n) catch return .void_type;
for (0..count) |r| {
for (0..li.field_types.len) |f| {
ft[r * li.field_types.len + f] = li.field_types[f];
}
}
return .{ .tuple_type = .{ .field_types = ft, .field_names = null } };
}
return Type.widen(lhs_ty, rhs_ty);
},
}
@@ -7628,6 +8071,12 @@ pub const CodeGen = struct {
if (obj_ty.isVector()) {
return obj_ty.vectorElementType() orelse Type.s(64);
}
if (obj_ty.isTuple()) {
const ti = obj_ty.tuple_type;
if (resolveTupleFieldIndex(ti, fa.field)) |idx| {
return ti.field_types[idx];
}
}
if (obj_ty.isStruct()) {
if (self.lookupStructInfo(obj_ty.struct_type)) |info| {
if (self.findNameIndex(info.field_names, fa.field)) |idx| {
@@ -7688,6 +8137,20 @@ pub const CodeGen = struct {
if (sl.struct_name) |sname| return .{ .struct_type = sname };
return Type.s(64);
},
.tuple_literal => |tl| {
const field_types = self.allocator.alloc(Type, tl.elements.len) catch return Type.s(64);
for (tl.elements, 0..) |elem, i| {
field_types[i] = self.inferType(elem.value);
}
const field_names: ?[]const []const u8 = if (tl.elements.len > 0 and tl.elements[0].name != null) blk: {
const names = self.allocator.alloc([]const u8, tl.elements.len) catch return Type.s(64);
for (tl.elements, 0..) |elem, i| {
names[i] = elem.name orelse "";
}
break :blk names;
} else null;
return .{ .tuple_type = .{ .field_names = field_names, .field_types = field_types } };
},
.while_expr, .for_expr, .break_expr, .continue_expr => .void_type,
else => Type.s(64),
};