This commit is contained in:
agra
2026-02-20 18:22:42 +02:00
parent 6f927361aa
commit 2f95810f9d
10 changed files with 510 additions and 77 deletions

View File

@@ -99,6 +99,15 @@ fn baseName(name: []const u8) []const u8 {
return if (std.mem.lastIndexOfScalar(u8, name, '.')) |idx| name[idx + 1 ..] else name;
}
/// Detect `$T: Type` parameter declarations (not `s: $T` references).
/// For `$T: Type`, the parser sets param.name = "T" and type_expr = {name="T", is_generic=true}.
/// For `s: $T`, param.name = "s" and type_expr = {name="T", is_generic=true}.
fn isTypeParamDecl(param: ast.Param) bool {
return param.type_expr.data == .type_expr and
param.type_expr.data.type_expr.is_generic and
std.mem.eql(u8, param.name, param.type_expr.data.type_expr.name);
}
pub const CodeGen = struct {
context: c.LLVMContextRef,
module: c.LLVMModuleRef,
@@ -1271,6 +1280,7 @@ pub const CodeGen = struct {
};
var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self);
vm.setupComptimeContext() catch {};
return vm.execute(&chunk) catch |err| {
return self.emitErrorFmt("comptime execution failed: {s}", .{@errorName(err)});
};
@@ -1307,6 +1317,7 @@ pub const CodeGen = struct {
// Set up VM and push all args onto the stack
var vm = comptime_mod.VM.init(self.allocator, if (self.sema_result) |sr| sr else null, self.root_decls, self);
vm.setupComptimeContext() catch {};
for (arg_values) |val| {
vm.push(val) catch return null;
}
@@ -1842,6 +1853,8 @@ pub const CodeGen = struct {
var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty;
for (params) |param| {
if (param.is_comptime) continue;
// Skip $T: Type params — erased at instantiation time (param name == type name)
if (isTypeParamDecl(param)) continue;
if (param.is_variadic) {
// Variadic param becomes a slice {ptr, i32} in the LLVM signature
try param_llvm_types.append(self.allocator, self.getStringStructType());
@@ -3333,6 +3346,26 @@ pub const CodeGen = struct {
return self.emitErrorFmt("field assignment not supported on tagged enum '{s}'", .{uname});
}
// Slice/string field assignment: s.ptr = val, s.len = val
if (entry.ty == .string_type or entry.ty.isSlice()) {
const struct_ty = self.getStringStructType();
if (std.mem.eql(u8, fa.field, "ptr")) {
const gep = self.structGEP(struct_ty, entry.ptr, 0, "slice_ptr");
const elem_name = if (entry.ty == .string_type) "u8" else entry.ty.slice_type.element_name;
const ptr_ty = Type{ .many_pointer_type = .{ .element_name = elem_name } };
const rhs = try self.genExprAsType(asgn.value, ptr_ty);
_ = c.LLVMBuildStore(self.builder, rhs, gep);
return null;
}
if (std.mem.eql(u8, fa.field, "len")) {
const gep = self.structGEP(struct_ty, entry.ptr, 1, "slice_len");
const rhs = try self.genExprAsType(asgn.value, Type.s(64));
_ = c.LLVMBuildStore(self.builder, rhs, gep);
return null;
}
return self.emitErrorFmt("no field '{s}' on slice (available: .ptr, .len)", .{fa.field});
}
if (!entry.ty.isStruct()) return self.emitErrorFmt("field access on non-struct variable '{s}'", .{obj_name});
const sname = entry.ty.struct_type;
@@ -3741,6 +3774,11 @@ pub const CodeGen = struct {
Type.u(8);
return self.gepPointerElement(self.typeToLLVM(elem_ty), ptr, idx, "addr_elem");
}
if (obj_ty.isManyPointer()) {
const raw_ptr = try self.genExpr(ie.object);
const elem_ty = self.resolveTypeFromName(obj_ty.many_pointer_type.element_name) orelse Type.u(8);
return self.gepPointerElement(self.typeToLLVM(elem_ty), raw_ptr, idx, "addr_elem");
}
}
// &s.field — return GEP pointer to the struct field
if (operand.data == .field_access) {
@@ -6072,6 +6110,26 @@ pub const CodeGen = struct {
}
}
// Struct field function pointer call: obj.field(args)
// Checked before UFCS so that struct fields shadow free functions of the same name.
{
var obj_ty = self.inferType(fa.object);
if (obj_ty.isPointer()) {
obj_ty = self.resolveTypeFromName(obj_ty.pointer_type.pointee_name) orelse obj_ty;
}
if (obj_ty.isStruct()) {
if (self.lookupStructInfo(obj_ty.struct_type)) |info| {
if (self.findNameIndex(info.field_names, fa.field)) |idx| {
const field_ty = info.field_types[idx];
if (field_ty.isFunctionType()) {
const fn_ptr = try self.genFieldAccess(fa);
return self.genIndirectCallFromPtr(fn_ptr, field_ty.function_type, call_node);
}
}
}
}
}
// UFCS: obj.method(args...) → method(obj, args...)
const method_name = fa.field;
const resolved_method = self.ufcs_aliases.get(method_name) orelse method_name;
@@ -6373,11 +6431,12 @@ pub const CodeGen = struct {
fn genIndirectCall(self: *CodeGen, entry: NamedValue, call_node: ast.Call) !c.LLVMValueRef {
const fti = entry.ty.function_type;
// Load the function pointer from the alloca
const ptr_ty = self.ptrType();
const fn_ptr = c.LLVMBuildLoad2(self.builder, ptr_ty, entry.ptr, "fn_ptr");
return self.genIndirectCallFromPtr(fn_ptr, fti, call_node);
}
fn genIndirectCallFromPtr(self: *CodeGen, fn_ptr: c.LLVMValueRef, fti: Type.FunctionTypeInfo, call_node: ast.Call) !c.LLVMValueRef {
// Build LLVM function type from FunctionTypeInfo
const ptr_ty_llvm = self.ptrType();
if (fti.param_types.len > 64) return self.emitErrorFmt("indirect call has {d} parameters, exceeding maximum of 64", .{fti.param_types.len});
@@ -6482,6 +6541,22 @@ pub const CodeGen = struct {
var bindings = std.StringHashMap(Type).init(self.allocator);
// Track bindings derived from parameterized struct types — these are authoritative and should not be widened
var firm_bindings = std.StringHashMap(void).init(self.allocator);
// Bind explicit $T: Type params from type expression args
for (fd.params, 0..) |param, i| {
if (!param.is_comptime) continue;
if (i >= call_node.args.len) continue;
const arg = call_node.args[i];
if (arg.data != .type_expr) continue;
for (fd.type_params) |tp| {
if (std.mem.eql(u8, tp.name, param.name)) {
const constraint = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else "";
if (std.mem.eql(u8, constraint, "Type")) {
try bindings.put(tp.name, self.resolveType(arg));
}
break;
}
}
}
for (fd.params, 0..) |param, i| {
if (param.is_comptime) continue;
// Direct type param: (a: $T) introduces/widens, (a: T) only binds if not yet bound
@@ -6580,10 +6655,16 @@ pub const CodeGen = struct {
try self.instantiateGeneric(fd, bindings, mangled);
// Generate arguments with type conversion to match parameter types
// Skip $T: Type params (arg is a type expression, not a runtime value)
const saved_call_bindings = self.type_param_bindings;
self.type_param_bindings = bindings;
var arg_vals = std.ArrayList(c.LLVMValueRef).empty;
for (call_node.args, 0..) |arg, i| {
// Skip $T: Type params — the arg is a type expression, not a runtime value
if (i < fd.params.len) {
const p = fd.params[i];
if (isTypeParamDecl(p)) continue;
}
if (i < fd.params.len) {
const param_ty = self.resolveType(fd.params[i].type_expr);
try arg_vals.append(self.allocator, try self.genExprAsType(arg, param_ty));
@@ -6979,6 +7060,8 @@ pub const CodeGen = struct {
// Create allocas for parameters
var llvm_param_idx: u32 = 0;
for (fd.params) |param| {
// Skip $T: Type params — type is resolved via bindings, not passed at runtime
if (isTypeParamDecl(param)) continue;
if (param.is_comptime) {
// Comptime param: create a constant in named_values from the call-site value
if (self.comptime_param_nodes) |cpn| {
@@ -7930,6 +8013,24 @@ pub const CodeGen = struct {
break :blk @as(?Type, null);
};
if (obj_ty) |uty| return uty;
// Struct field function pointer call: obj.fn_field(args)
{
var fa_obj_ty = self.inferType(fa.object);
if (fa_obj_ty.isPointer()) {
fa_obj_ty = self.resolveTypeFromName(fa_obj_ty.pointer_type.pointee_name) orelse fa_obj_ty;
}
if (fa_obj_ty.isStruct()) {
if (self.lookupStructInfo(fa_obj_ty.struct_type)) |info| {
if (self.findNameIndex(info.field_names, fa.field)) |idx| {
const field_ty = info.field_types[idx];
if (field_ty.isFunctionType()) {
return field_ty.function_type.return_type.*;
}
}
}
}
}
}
const callee_name = self.resolveCalleeName(call_node) orelse return Type.s(64);
const base_name = baseName(callee_name);
@@ -8005,8 +8106,11 @@ pub const CodeGen = struct {
return bound_ty;
}
}
// Try resolving as a concrete type (e.g. -> string, -> s32)
// Resolve with inferred bindings so []T, *T etc. substitute correctly
const saved_bindings = self.type_param_bindings;
self.type_param_bindings = inferred_bindings;
const resolved = self.resolveType(rt);
self.type_param_bindings = saved_bindings;
if (!std.meta.eql(resolved, Type.void_type)) return resolved;
}
return Type.s(64);