diff --git a/examples/13-code.sx b/examples/13-code.sx index 7488446..14003b5 100644 --- a/examples/13-code.sx +++ b/examples/13-code.sx @@ -5,5 +5,5 @@ generate::() -> string { } main :: () { - #insert #run generate(); + #insert generate(); } \ No newline at end of file diff --git a/examples/modules/std.sx b/examples/modules/std.sx index f5c757e..2b269e0 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -11,6 +11,7 @@ type_name :: ($T: Type) -> string #builtin; field_count :: ($T: Type) -> s64 #builtin; field_name :: ($T: Type, idx: s64) -> string #builtin; field_value :: (s: $T, idx: s64) -> Any #builtin; +string :: []u8 #builtin; int_to_string :: (n: s64) -> string { if n == 0 { return "0"; } diff --git a/src/codegen.zig b/src/codegen.zig index fbb20bb..85bf548 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -95,6 +95,7 @@ pub const CodeGen = struct { const DeferredFn = struct { fd: ast.FnDecl, name: []const u8, // qualified name (may differ from fd.name for namespaced functions) + namespace: ?[]const u8 = null, }; const TypeCategory = enum { @@ -665,6 +666,9 @@ pub const CodeGen = struct { // Pass 3: Compile deferred function bodies (after all types are registered) for (self.deferred_fn_bodies.items) |deferred| { + const saved_ns = self.current_namespace; + self.current_namespace = deferred.namespace; + defer self.current_namespace = saved_ns; try self.genFnBodyAs(deferred.fd, deferred.name); } @@ -920,27 +924,7 @@ pub const CodeGen = struct { } } - // Build mangled name: Vec__3_f32 - var mangle_buf = std.ArrayList(u8).empty; - try mangle_buf.appendSlice(self.allocator, template_name); - try mangle_buf.appendSlice(self.allocator, "__"); - for (sd.type_params, 0..) |tp, i| { - if (i > 0) try mangle_buf.append(self.allocator, '_'); - const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; - if (std.mem.eql(u8, constraint_name, "Type")) { - if (type_bindings.get(tp.name)) |ty| { - const dn = ty.displayName(self.allocator) catch "?"; - try mangle_buf.appendSlice(self.allocator, dn); - } - } else { - if (val_bindings.get(tp.name)) |val| { - var tmp: [20]u8 = undefined; - const s = std.fmt.bufPrint(&tmp, "{d}", .{val}) catch "0"; - try mangle_buf.appendSlice(self.allocator, s); - } - } - } - const mangled_name = try mangle_buf.toOwnedSlice(self.allocator); + const mangled_name = try self.mangleGenericName(template_name, sd.type_params, type_bindings, val_bindings, null); // Check if already instantiated if (self.struct_types.contains(mangled_name)) { @@ -957,24 +941,8 @@ pub const CodeGen = struct { self.value_param_bindings = saved_value_bindings; } - var field_sx_types = std.ArrayList(Type).empty; - var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; - - for (sd.field_types) |ft| { - const sx_ty = self.resolveType(ft); - try field_sx_types.append(self.allocator, sx_ty); - try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - - const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); - const name_z = try self.allocator.dupeZ(u8, mangled_name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); - - var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); - for (sd.field_defaults, 0..) |fd, i| { - resolved_defaults[i] = fd; - } + const build = try self.buildStructFields(mangled_name, sd.field_types); + const resolved_defaults = try self.allocator.dupe(?*Node, sd.field_defaults); // Build pretty display name: Vec(3,f32) var display_buf = std.ArrayList(u8).empty; @@ -1014,9 +982,9 @@ pub const CodeGen = struct { try self.struct_types.put(mangled_name, .{ .field_names = sd.field_names, - .field_types = try field_sx_types.toOwnedSlice(self.allocator), + .field_types = build.field_sx_types, .field_defaults = resolved_defaults, - .llvm_type = struct_ty, + .llvm_type = build.llvm_type, .display_name = display_name, .type_param_names = try tp_names.toOwnedSlice(self.allocator), .type_param_types = try tp_types.toOwnedSlice(self.allocator), @@ -1045,18 +1013,7 @@ pub const CodeGen = struct { self.type_param_bindings = type_bindings; defer self.type_param_bindings = saved_type_bindings; - // Build mangled name from template + args - var mangle_buf = std.ArrayList(u8).empty; - try mangle_buf.appendSlice(self.allocator, template_name); - try mangle_buf.appendSlice(self.allocator, "__"); - for (fd.type_params, 0..) |tp, i| { - if (i > 0) try mangle_buf.append(self.allocator, '_'); - if (type_bindings.get(tp.name)) |ty| { - const dn = ty.displayName(self.allocator) catch "?"; - try mangle_buf.appendSlice(self.allocator, dn); - } - } - const mangled_name = try mangle_buf.toOwnedSlice(self.allocator); + const mangled_name = try self.mangleGenericName(template_name, fd.type_params, type_bindings, null, null); // Try struct first if (self.findStructInBody(fd.body)) |struct_decl| { @@ -1078,34 +1035,16 @@ pub const CodeGen = struct { } fn registerInstantiatedStruct(self: *CodeGen, mangled_name: []const u8, alias_name: []const u8, struct_decl: ast.StructDecl) !Type { - var field_sx_types = std.ArrayList(Type).empty; - var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + const build = try self.buildStructFields(mangled_name, struct_decl.field_types); - for (struct_decl.field_types) |ft| { - const sx_ty = self.resolveType(ft); - try field_sx_types.append(self.allocator, sx_ty); - try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); - } - - const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); - const name_z = try self.allocator.dupeZ(u8, mangled_name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); - - var resolved_defaults = try self.allocator.alloc(?*Node, struct_decl.field_defaults.len); - for (struct_decl.field_defaults, 0..) |fd_def, i| { - resolved_defaults[i] = fd_def; - } - - var display_buf = std.ArrayList(u8).empty; - try display_buf.appendSlice(self.allocator, alias_name); - const display_name = try display_buf.toOwnedSlice(self.allocator); + const resolved_defaults = try self.allocator.dupe(?*Node, struct_decl.field_defaults); + const display_name = try self.allocator.dupe(u8, alias_name); try self.struct_types.put(mangled_name, .{ .field_names = struct_decl.field_names, - .field_types = try field_sx_types.toOwnedSlice(self.allocator), + .field_types = build.field_sx_types, .field_defaults = resolved_defaults, - .llvm_type = struct_ty, + .llvm_type = build.llvm_type, .display_name = display_name, }); _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); @@ -1114,35 +1053,13 @@ pub const CodeGen = struct { } fn registerInstantiatedUnion(self: *CodeGen, mangled_name: []const u8, union_decl: ast.UnionDecl) !Type { - var variant_sx_types = std.ArrayList(Type).empty; - var max_payload_size: u64 = 0; - const data_layout = c.LLVMGetModuleDataLayout(self.module); - - for (union_decl.variant_types) |vt| { - if (vt) |type_node| { - const sx_ty = self.resolveType(type_node); - try variant_sx_types.append(self.allocator, sx_ty); - const llvm_ty = self.typeToLLVM(sx_ty); - const size = c.LLVMStoreSizeOfType(data_layout, llvm_ty); - if (size > max_payload_size) max_payload_size = size; - } else { - try variant_sx_types.append(self.allocator, .void_type); - } - } - - const name_z = try self.allocator.dupeZ(u8, mangled_name); - const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); - const i64_ty = c.LLVMInt64TypeInContext(self.context); - const i8_ty = c.LLVMInt8TypeInContext(self.context); - const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); - var fields = [2]c.LLVMTypeRef{ i64_ty, payload_array_ty }; - c.LLVMStructSetBody(union_ty, &fields, 2, 0); + const build = try self.buildUnionFields(mangled_name, union_decl.variant_types); try self.union_types.put(mangled_name, .{ .variant_names = union_decl.variant_names, - .variant_types = try variant_sx_types.toOwnedSlice(self.allocator), - .llvm_type = union_ty, - .max_payload_size = max_payload_size, + .variant_types = build.variant_sx_types, + .llvm_type = build.llvm_type, + .max_payload_size = build.max_payload_size, }); _ = try self.getAnyTypeId(mangled_name, .{ .union_type = mangled_name }); @@ -1150,35 +1067,36 @@ pub const CodeGen = struct { } /// Walk an AST body to find a struct declaration (from `return struct { ... }` or bare struct expr). - fn findStructInBody(_: *CodeGen, body: *Node) ?ast.StructDecl { - if (body.data == .struct_decl) return body.data.struct_decl; + fn findDeclInBody(comptime T: type, comptime tag: std.meta.FieldEnum(@TypeOf(@as(Node, undefined).data)), body: *Node) ?T { + const extract = struct { + fn get(node: *Node) ?T { + return if (@field(node.data, @tagName(tag)) != @as(?T, null)) + @field(node.data, @tagName(tag)) + else + null; + } + }; + _ = extract; + if (body.data == tag) return @field(body.data, @tagName(tag)); if (body.data == .block) { for (body.data.block.stmts) |stmt| { if (stmt.data == .return_stmt) { if (stmt.data.return_stmt.value) |val| { - if (val.data == .struct_decl) return val.data.struct_decl; + if (val.data == tag) return @field(val.data, @tagName(tag)); } } - if (stmt.data == .struct_decl) return stmt.data.struct_decl; + if (stmt.data == tag) return @field(stmt.data, @tagName(tag)); } } return null; } - /// Walk an AST body to find a union declaration (from `return union { ... }` or bare union expr). + fn findStructInBody(_: *CodeGen, body: *Node) ?ast.StructDecl { + return findDeclInBody(ast.StructDecl, .struct_decl, body); + } + fn findUnionInBody(_: *CodeGen, body: *Node) ?ast.UnionDecl { - if (body.data == .union_decl) return body.data.union_decl; - if (body.data == .block) { - for (body.data.block.stmts) |stmt| { - if (stmt.data == .return_stmt) { - if (stmt.data.return_stmt.value) |val| { - if (val.data == .union_decl) return val.data.union_decl; - } - } - if (stmt.data == .union_decl) return stmt.data.union_decl; - } - } - return null; + return findDeclInBody(ast.UnionDecl, .union_decl, body); } fn buildFnType(self: *CodeGen, params: []const ast.Param, return_type: ?*Node, name: []const u8) !c.LLVMTypeRef { @@ -1296,7 +1214,7 @@ pub const CodeGen = struct { } else if (fd.type_params.len == 0) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); if (shouldDeferFnBody(fd)) { - try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified }); + try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = qualified, .namespace = ns.name }); } else { try self.genFnBodyAs(fd, qualified); } @@ -1428,6 +1346,15 @@ pub const CodeGen = struct { }); } + fn bindParam(self: *CodeGen, function: c.LLVMValueRef, name: []const u8, sx_ty: Type, param_idx: u32) !void { + const llvm_ty = self.typeToLLVM(sx_ty); + const param_name_z = try self.allocator.dupeZ(u8, name); + const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); + const param_val = c.LLVMGetParam(function, param_idx); + _ = c.LLVMBuildStore(self.builder, param_val, alloca); + try self.named_values.put(name, .{ .ptr = alloca, .ty = sx_ty }); + } + fn genFnBody(self: *CodeGen, fd: ast.FnDecl) !void { return self.genFnBodyAs(fd, fd.name); } @@ -1458,12 +1385,7 @@ pub const CodeGen = struct { break :blk Type{ .slice_type = .{ .element_name = elem_name } }; } else self.resolveType(param.type_expr); if (sx_ty == .void_type) return self.emitErrorFmt("parameter '{s}' has unresolved type", .{param.name}); - const llvm_ty = self.typeToLLVM(sx_ty); - const param_name_z = try self.allocator.dupeZ(u8, param.name); - const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); - const param_val = c.LLVMGetParam(function, @intCast(i)); - _ = c.LLVMBuildStore(self.builder, param_val, alloca); - try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + try self.bindParam(function, param.name, sx_ty, @intCast(i)); } // Push function-level scope so that function-body defers are tracked @@ -1554,12 +1476,7 @@ pub const CodeGen = struct { for (lambda.params, 0..) |param, i| { const sx_ty = self.resolveType(param.type_expr); - const llvm_ty = self.typeToLLVM(sx_ty); - const param_name_z = try self.allocator.dupeZ(u8, param.name); - const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); - const param_val = c.LLVMGetParam(function, @intCast(i)); - _ = c.LLVMBuildStore(self.builder, param_val, alloca); - try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + try self.bindParam(function, param.name, sx_ty, @intCast(i)); } const ret_val = try self.genExpr(lambda.body); @@ -1610,12 +1527,7 @@ pub const CodeGen = struct { for (fd.params, 0..) |param, i| { const sx_ty = self.resolveType(param.type_expr); - const llvm_ty = self.typeToLLVM(sx_ty); - const param_name_z = try self.allocator.dupeZ(u8, param.name); - const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); - const param_val = c.LLVMGetParam(function, @intCast(i)); - _ = c.LLVMBuildStore(self.builder, param_val, alloca); - try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + try self.bindParam(function, param.name, sx_ty, @intCast(i)); } var last_val: c.LLVMValueRef = null; @@ -2007,6 +1919,17 @@ pub const CodeGen = struct { return null; } + fn genCompoundOp(self: *CodeGen, op: ast.Assignment.Op, cur: c.LLVMValueRef, rhs: c.LLVMValueRef, ty: Type) c.LLVMValueRef { + return switch (op) { + .add_assign => if (ty.isFloat()) c.LLVMBuildFAdd(self.builder, cur, rhs, "addtmp") else c.LLVMBuildAdd(self.builder, cur, rhs, "addtmp"), + .sub_assign => if (ty.isFloat()) c.LLVMBuildFSub(self.builder, cur, rhs, "subtmp") else c.LLVMBuildSub(self.builder, cur, rhs, "subtmp"), + .mul_assign => if (ty.isFloat()) c.LLVMBuildFMul(self.builder, cur, rhs, "multmp") else c.LLVMBuildMul(self.builder, cur, rhs, "multmp"), + .div_assign => if (ty.isFloat()) c.LLVMBuildFDiv(self.builder, cur, rhs, "divtmp") else if (ty.isUnsigned()) c.LLVMBuildUDiv(self.builder, cur, rhs, "divtmp") else c.LLVMBuildSDiv(self.builder, cur, rhs, "divtmp"), + .mod_assign => if (ty.isFloat()) c.LLVMBuildFRem(self.builder, cur, rhs, "modtmp") else if (ty.isUnsigned()) c.LLVMBuildURem(self.builder, cur, rhs, "modtmp") else c.LLVMBuildSRem(self.builder, cur, rhs, "modtmp"), + .assign => unreachable, + }; + } + fn genAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { // Field assignment: expr.field = value; if (asgn.target.data == .field_access) { @@ -2072,47 +1995,9 @@ pub const CodeGen = struct { const new_val = try self.genExpr(asgn.value); const llvm_ty = self.typeToLLVM(entry.ty); - const store_val = switch (asgn.op) { - .assign => new_val, - .add_assign => blk: { - const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); - break :blk if (entry.ty.isFloat()) - c.LLVMBuildFAdd(self.builder, cur, new_val, "addtmp") - else - c.LLVMBuildAdd(self.builder, cur, new_val, "addtmp"); - }, - .sub_assign => blk: { - const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); - break :blk if (entry.ty.isFloat()) - c.LLVMBuildFSub(self.builder, cur, new_val, "subtmp") - else - c.LLVMBuildSub(self.builder, cur, new_val, "subtmp"); - }, - .mul_assign => blk: { - const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); - break :blk if (entry.ty.isFloat()) - c.LLVMBuildFMul(self.builder, cur, new_val, "multmp") - else - c.LLVMBuildMul(self.builder, cur, new_val, "multmp"); - }, - .div_assign => blk: { - const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); - break :blk if (entry.ty.isFloat()) - c.LLVMBuildFDiv(self.builder, cur, new_val, "divtmp") - else if (entry.ty.isUnsigned()) - c.LLVMBuildUDiv(self.builder, cur, new_val, "divtmp") - else - c.LLVMBuildSDiv(self.builder, cur, new_val, "divtmp"); - }, - .mod_assign => blk: { - const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); - break :blk if (entry.ty.isFloat()) - c.LLVMBuildFRem(self.builder, cur, new_val, "modtmp") - else if (entry.ty.isUnsigned()) - c.LLVMBuildURem(self.builder, cur, new_val, "modtmp") - else - c.LLVMBuildSRem(self.builder, cur, new_val, "modtmp"); - }, + const store_val = if (asgn.op == .assign) new_val else blk: { + const cur = c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "cur"); + break :blk self.genCompoundOp(asgn.op, cur, new_val, entry.ty); }; _ = c.LLVMBuildStore(self.builder, store_val, entry.ptr); @@ -2122,12 +2007,6 @@ pub const CodeGen = struct { fn genFieldAssignment(self: *CodeGen, asgn: ast.Assignment) !c.LLVMValueRef { const fa = asgn.target.data.field_access; - // Handle deref assignment: p.* = val - if (fa.object.data == .identifier and std.mem.eql(u8, fa.field, "*")) { - // This won't happen — p.* is parsed as deref_expr, not field_access - // Kept as safeguard - } - // Object must be an identifier for now if (fa.object.data != .identifier) return self.emitError("field assignment target must be a variable"); const obj_name = fa.object.data.identifier.name; @@ -2151,15 +2030,7 @@ pub const CodeGen = struct { } else { const field_llvm_ty = self.typeToLLVM(field_ty); const cur = c.LLVMBuildLoad2(self.builder, field_llvm_ty, gep, "pcur"); - const store_val = switch (asgn.op) { - .add_assign => if (field_ty.isFloat()) c.LLVMBuildFAdd(self.builder, cur, rhs, "paddtmp") else c.LLVMBuildAdd(self.builder, cur, rhs, "paddtmp"), - .sub_assign => if (field_ty.isFloat()) c.LLVMBuildFSub(self.builder, cur, rhs, "psubtmp") else c.LLVMBuildSub(self.builder, cur, rhs, "psubtmp"), - .mul_assign => if (field_ty.isFloat()) c.LLVMBuildFMul(self.builder, cur, rhs, "pmultmp") else c.LLVMBuildMul(self.builder, cur, rhs, "pmultmp"), - .div_assign => if (field_ty.isFloat()) c.LLVMBuildFDiv(self.builder, cur, rhs, "pdivtmp") else if (field_ty.isUnsigned()) c.LLVMBuildUDiv(self.builder, cur, rhs, "pdivtmp") else c.LLVMBuildSDiv(self.builder, cur, rhs, "pdivtmp"), - .mod_assign => if (field_ty.isFloat()) c.LLVMBuildFRem(self.builder, cur, rhs, "pmodtmp") else if (field_ty.isUnsigned()) c.LLVMBuildURem(self.builder, cur, rhs, "pmodtmp") else c.LLVMBuildSRem(self.builder, cur, rhs, "pmodtmp"), - .assign => unreachable, - }; - _ = c.LLVMBuildStore(self.builder, store_val, gep); + _ = c.LLVMBuildStore(self.builder, self.genCompoundOp(asgn.op, cur, rhs, field_ty), gep); } return null; } @@ -2181,37 +2052,9 @@ pub const CodeGen = struct { if (asgn.op == .assign) { _ = c.LLVMBuildStore(self.builder, rhs, gep); } else { - // Compound assignment on struct field const field_llvm_ty = self.typeToLLVM(field_ty); const cur = c.LLVMBuildLoad2(self.builder, field_llvm_ty, gep, "fcur"); - const store_val = switch (asgn.op) { - .add_assign => if (field_ty.isFloat()) - c.LLVMBuildFAdd(self.builder, cur, rhs, "faddtmp") - else - c.LLVMBuildAdd(self.builder, cur, rhs, "faddtmp"), - .sub_assign => if (field_ty.isFloat()) - c.LLVMBuildFSub(self.builder, cur, rhs, "fsubtmp") - else - c.LLVMBuildSub(self.builder, cur, rhs, "fsubtmp"), - .mul_assign => if (field_ty.isFloat()) - c.LLVMBuildFMul(self.builder, cur, rhs, "fmultmp") - else - c.LLVMBuildMul(self.builder, cur, rhs, "fmultmp"), - .div_assign => if (field_ty.isFloat()) - c.LLVMBuildFDiv(self.builder, cur, rhs, "fdivtmp") - else if (field_ty.isUnsigned()) - c.LLVMBuildUDiv(self.builder, cur, rhs, "fdivtmp") - else - c.LLVMBuildSDiv(self.builder, cur, rhs, "fdivtmp"), - .mod_assign => if (field_ty.isFloat()) - c.LLVMBuildFRem(self.builder, cur, rhs, "fmodtmp") - else if (field_ty.isUnsigned()) - c.LLVMBuildURem(self.builder, cur, rhs, "fmodtmp") - else - c.LLVMBuildSRem(self.builder, cur, rhs, "fmodtmp"), - .assign => unreachable, - }; - _ = c.LLVMBuildStore(self.builder, store_val, gep); + _ = c.LLVMBuildStore(self.builder, self.genCompoundOp(asgn.op, cur, rhs, field_ty), gep); } return null; } @@ -2235,15 +2078,12 @@ pub const CodeGen = struct { if (obj_ty.isArray()) { if (ie.object.data == .identifier) { if (self.named_values.get(ie.object.data.identifier.name)) |entry| { - const arr_info = obj_ty.array_type; - const elem_ty = Type.fromName(arr_info.element_name) orelse return self.emitError("unknown array element type"); const idx = try self.genExpr(ie.index); const val = try self.genExpr(asgn.value); const zero = c.LLVMConstInt(c.LLVMInt32TypeInContext(self.context), 0, 0); var indices = [_]c.LLVMValueRef{ zero, idx }; const gep_ptr = c.LLVMBuildGEP2(self.builder, self.typeToLLVM(obj_ty), entry.ptr, &indices, 2, "arridx"); _ = c.LLVMBuildStore(self.builder, val, gep_ptr); - _ = elem_ty; return null; } } @@ -2568,115 +2408,44 @@ pub const CodeGen = struct { return self.emitError("address-of requires a variable, index, or field expression"); } - fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void { - // Generic struct: store as template instead of registering now - if (sd.type_params.len > 0) { - try self.generic_struct_templates.put(sd.name, .{ .sd = sd }); - return; - } - - // Pre-pass: hoist inline type declarations from field types - for (sd.field_types, 0..) |ft, i| { - switch (ft.data) { - .struct_decl => |inline_sd| { - const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, sd.field_names[i] }); - var hoisted = inline_sd; - hoisted.name = synthetic_name; - try self.registerStructType(hoisted); - ft.data = .{ .type_expr = .{ .name = synthetic_name } }; - }, - .union_decl => |inline_ud| { - const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, sd.field_names[i] }); - var hoisted = inline_ud; - hoisted.name = synthetic_name; - try self.registerUnionType(hoisted); - ft.data = .{ .type_expr = .{ .name = synthetic_name } }; - }, - .enum_decl => |inline_ed| { - const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ sd.name, sd.field_names[i] }); - try self.enum_types.put(synthetic_name, inline_ed.variants); - _ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name }); - ft.data = .{ .type_expr = .{ .name = synthetic_name } }; - }, - else => {}, - } - } + const StructBuildResult = struct { + field_sx_types: []const Type, + llvm_type: c.LLVMTypeRef, + }; + fn buildStructFields(self: *CodeGen, name: []const u8, field_type_nodes: []const *Node) !StructBuildResult { var field_sx_types = std.ArrayList(Type).empty; var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; - for (sd.field_types) |ft| { + for (field_type_nodes) |ft| { const sx_ty = self.resolveType(ft); try field_sx_types.append(self.allocator, sx_ty); try field_llvm_types.append(self.allocator, self.typeToLLVM(sx_ty)); } const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); - const name_z = try self.allocator.dupeZ(u8, sd.name); + const name_z = try self.allocator.dupeZ(u8, name); const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); c.LLVMStructSetBody(struct_ty, if (llvm_types_slice.len > 0) llvm_types_slice.ptr else null, @intCast(llvm_types_slice.len), 0); - // Process field defaults: replace #run expressions with comptime global references - var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); - for (sd.field_defaults, 0..) |fd, i| { - if (fd != null and fd.?.data == .comptime_expr) { - // Register as anonymous comptime global for JIT evaluation - const synthetic_name = try std.fmt.allocPrint(self.allocator, "__struct_{s}_field_{d}", .{ sd.name, i }); - const field_type_override: ?Type = if (i < field_sx_types.items.len) field_sx_types.items[i] else null; - try self.registerComptimeGlobal(synthetic_name, fd.?.data.comptime_expr.expr, field_type_override); - // Replace with identifier node referencing the comptime global - const id_node = try self.allocator.create(Node); - id_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = synthetic_name } } }; - resolved_defaults[i] = id_node; - } else { - resolved_defaults[i] = fd; - } - } - - try self.struct_types.put(sd.name, .{ - .field_names = sd.field_names, - .field_types = try field_sx_types.toOwnedSlice(self.allocator), - .field_defaults = resolved_defaults, + return .{ + .field_sx_types = try field_sx_types.toOwnedSlice(self.allocator), .llvm_type = struct_ty, - }); - _ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name }); + }; } - fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { - // Pre-pass: hoist inline type declarations from variant types - for (ud.variant_types, 0..) |vt_opt, i| { - if (vt_opt) |vt| { - switch (vt.data) { - .struct_decl => |inline_sd| { - const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ud.name, ud.variant_names[i] }); - var hoisted = inline_sd; - hoisted.name = synthetic_name; - try self.registerStructType(hoisted); - vt.data = .{ .type_expr = .{ .name = synthetic_name } }; - }, - .union_decl => |inline_ud| { - const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ud.name, ud.variant_names[i] }); - var hoisted = inline_ud; - hoisted.name = synthetic_name; - try self.registerUnionType(hoisted); - vt.data = .{ .type_expr = .{ .name = synthetic_name } }; - }, - .enum_decl => |inline_ed| { - const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ud.name, ud.variant_names[i] }); - try self.enum_types.put(synthetic_name, inline_ed.variants); - _ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name }); - vt.data = .{ .type_expr = .{ .name = synthetic_name } }; - }, - else => {}, - } - } - } + const UnionBuildResult = struct { + variant_sx_types: []const Type, + llvm_type: c.LLVMTypeRef, + max_payload_size: u64, + }; + fn buildUnionFields(self: *CodeGen, name: []const u8, variant_type_nodes: []const ?*Node) !UnionBuildResult { var variant_sx_types = std.ArrayList(Type).empty; var max_payload_size: u64 = 0; const data_layout = c.LLVMGetModuleDataLayout(self.module); - for (ud.variant_types) |vt| { + for (variant_type_nodes) |vt| { if (vt) |type_node| { const sx_ty = self.resolveType(type_node); try variant_sx_types.append(self.allocator, sx_ty); @@ -2688,20 +2457,98 @@ pub const CodeGen = struct { } } - // Union LLVM type: { i64, [max_payload_size x i8] } - const name_z = try self.allocator.dupeZ(u8, ud.name); + const name_z = try self.allocator.dupeZ(u8, name); const union_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); const i64_ty = c.LLVMInt64TypeInContext(self.context); const i8_ty = c.LLVMInt8TypeInContext(self.context); const payload_array_ty = c.LLVMArrayType2(i8_ty, max_payload_size); - var fields = [2]c.LLVMTypeRef{ i64_ty, payload_array_ty }; - c.LLVMStructSetBody(union_ty, &fields, 2, 0); + var union_fields = [2]c.LLVMTypeRef{ i64_ty, payload_array_ty }; + c.LLVMStructSetBody(union_ty, &union_fields, 2, 0); + + return .{ + .variant_sx_types = try variant_sx_types.toOwnedSlice(self.allocator), + .llvm_type = union_ty, + .max_payload_size = max_payload_size, + }; + } + + fn hoistInlineTypeDecl(self: *CodeGen, parent_name: []const u8, child_name: []const u8, type_node: *Node) anyerror!void { + const synthetic_name = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ parent_name, child_name }); + switch (type_node.data) { + .struct_decl => |inline_sd| { + var hoisted = inline_sd; + hoisted.name = synthetic_name; + try self.registerStructType(hoisted); + type_node.data = .{ .type_expr = .{ .name = synthetic_name } }; + }, + .union_decl => |inline_ud| { + var hoisted = inline_ud; + hoisted.name = synthetic_name; + try self.registerUnionType(hoisted); + type_node.data = .{ .type_expr = .{ .name = synthetic_name } }; + }, + .enum_decl => |inline_ed| { + try self.enum_types.put(synthetic_name, inline_ed.variants); + _ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name }); + type_node.data = .{ .type_expr = .{ .name = synthetic_name } }; + }, + else => {}, + } + } + + fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void { + // Generic struct: store as template instead of registering now + if (sd.type_params.len > 0) { + try self.generic_struct_templates.put(sd.name, .{ .sd = sd }); + return; + } + + // Pre-pass: hoist inline type declarations from field types + for (sd.field_types, 0..) |ft, i| { + try self.hoistInlineTypeDecl(sd.name, sd.field_names[i], ft); + } + + const build = try self.buildStructFields(sd.name, sd.field_types); + + // Process field defaults: replace #run expressions with comptime global references + var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); + for (sd.field_defaults, 0..) |fd, i| { + if (fd != null and fd.?.data == .comptime_expr) { + const synthetic_name = try std.fmt.allocPrint(self.allocator, "__struct_{s}_field_{d}", .{ sd.name, i }); + const field_type_override: ?Type = if (i < build.field_sx_types.len) build.field_sx_types[i] else null; + try self.registerComptimeGlobal(synthetic_name, fd.?.data.comptime_expr.expr, field_type_override); + const id_node = try self.allocator.create(Node); + id_node.* = .{ .span = .{ .start = 0, .end = 0 }, .data = .{ .identifier = .{ .name = synthetic_name } } }; + resolved_defaults[i] = id_node; + } else { + resolved_defaults[i] = fd; + } + } + + try self.struct_types.put(sd.name, .{ + .field_names = sd.field_names, + .field_types = build.field_sx_types, + .field_defaults = resolved_defaults, + .llvm_type = build.llvm_type, + }); + _ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name }); + } + + fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { + // Pre-pass: hoist inline type declarations from variant types + for (ud.variant_types, 0..) |vt_opt, i| { + if (vt_opt) |vt| { + try self.hoistInlineTypeDecl(ud.name, ud.variant_names[i], vt); + } + } + + const build = try self.buildUnionFields(ud.name, ud.variant_types); try self.union_types.put(ud.name, .{ .variant_names = ud.variant_names, - .variant_types = try variant_sx_types.toOwnedSlice(self.allocator), - .llvm_type = union_ty, - .max_payload_size = max_payload_size, + .variant_types = build.variant_sx_types, + .llvm_type = build.llvm_type, + .max_payload_size = build.max_payload_size, }); _ = try self.getAnyTypeId(ud.name, .{ .union_type = ud.name }); } @@ -3868,88 +3715,35 @@ pub const CodeGen = struct { } fn genBinaryOp(self: *CodeGen, op: ast.BinaryOp.Op, lhs: c.LLVMValueRef, rhs: c.LLVMValueRef, result_type: Type) c.LLVMValueRef { - // Vector types: dispatch based on element type (LLVM does element-wise automatically) - if (result_type.isVector()) { - const elem_ty = result_type.vectorElementType() orelse return lhs; - if (op == .eq or op == .neq) { - return self.genVectorComparison(op, lhs, rhs, result_type, elem_ty); - } - if (elem_ty.isFloat()) { - return switch (op) { - .add => c.LLVMBuildFAdd(self.builder, lhs, rhs, "vaddtmp"), - .sub => c.LLVMBuildFSub(self.builder, lhs, rhs, "vsubtmp"), - .mul => c.LLVMBuildFMul(self.builder, lhs, rhs, "vmultmp"), - .div => c.LLVMBuildFDiv(self.builder, lhs, rhs, "vdivtmp"), - .mod => c.LLVMBuildFRem(self.builder, lhs, rhs, "vmodtmp"), - else => lhs, - }; - } else if (elem_ty.isUnsigned()) { - return switch (op) { - .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "vaddtmp"), - .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "vsubtmp"), - .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "vmultmp"), - .div => c.LLVMBuildUDiv(self.builder, lhs, rhs, "vdivtmp"), - .mod => c.LLVMBuildURem(self.builder, lhs, rhs, "vmodtmp"), - else => lhs, - }; - } else { - return switch (op) { - .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "vaddtmp"), - .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "vsubtmp"), - .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "vmultmp"), - .div => c.LLVMBuildSDiv(self.builder, lhs, rhs, "vdivtmp"), - .mod => c.LLVMBuildSRem(self.builder, lhs, rhs, "vmodtmp"), - else => lhs, - }; - } - } - if (result_type.isFloat()) { - return switch (op) { - .add => c.LLVMBuildFAdd(self.builder, lhs, rhs, "addtmp"), - .sub => c.LLVMBuildFSub(self.builder, lhs, rhs, "subtmp"), - .mul => c.LLVMBuildFMul(self.builder, lhs, rhs, "multmp"), - .div => c.LLVMBuildFDiv(self.builder, lhs, rhs, "divtmp"), - .mod => c.LLVMBuildFRem(self.builder, lhs, rhs, "modtmp"), - .eq => c.LLVMBuildFCmp(self.builder, c.LLVMRealOEQ, lhs, rhs, "eqtmp"), - .neq => c.LLVMBuildFCmp(self.builder, c.LLVMRealONE, lhs, rhs, "neqtmp"), - .lt => c.LLVMBuildFCmp(self.builder, c.LLVMRealOLT, lhs, rhs, "lttmp"), - .lte => c.LLVMBuildFCmp(self.builder, c.LLVMRealOLE, lhs, rhs, "letmp"), - .gt => c.LLVMBuildFCmp(self.builder, c.LLVMRealOGT, lhs, rhs, "gttmp"), - .gte => c.LLVMBuildFCmp(self.builder, c.LLVMRealOGE, lhs, rhs, "getmp"), - .and_op, .or_op => unreachable, - }; - } else if (result_type.isUnsigned()) { - return switch (op) { - .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "addtmp"), - .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "subtmp"), - .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "multmp"), - .div => c.LLVMBuildUDiv(self.builder, lhs, rhs, "divtmp"), - .mod => c.LLVMBuildURem(self.builder, lhs, rhs, "modtmp"), - .eq => c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, lhs, rhs, "eqtmp"), - .neq => c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs, rhs, "neqtmp"), - .lt => c.LLVMBuildICmp(self.builder, c.LLVMIntULT, lhs, rhs, "lttmp"), - .lte => c.LLVMBuildICmp(self.builder, c.LLVMIntULE, lhs, rhs, "letmp"), - .gt => c.LLVMBuildICmp(self.builder, c.LLVMIntUGT, lhs, rhs, "gttmp"), - .gte => c.LLVMBuildICmp(self.builder, c.LLVMIntUGE, lhs, rhs, "getmp"), - .and_op, .or_op => unreachable, - }; - } else { - // signed int (default) - return switch (op) { - .add => c.LLVMBuildAdd(self.builder, lhs, rhs, "addtmp"), - .sub => c.LLVMBuildSub(self.builder, lhs, rhs, "subtmp"), - .mul => c.LLVMBuildMul(self.builder, lhs, rhs, "multmp"), - .div => c.LLVMBuildSDiv(self.builder, lhs, rhs, "divtmp"), - .mod => c.LLVMBuildSRem(self.builder, lhs, rhs, "modtmp"), - .eq => c.LLVMBuildICmp(self.builder, c.LLVMIntEQ, lhs, rhs, "eqtmp"), - .neq => c.LLVMBuildICmp(self.builder, c.LLVMIntNE, lhs, rhs, "neqtmp"), - .lt => c.LLVMBuildICmp(self.builder, c.LLVMIntSLT, lhs, rhs, "lttmp"), - .lte => c.LLVMBuildICmp(self.builder, c.LLVMIntSLE, lhs, rhs, "letmp"), - .gt => c.LLVMBuildICmp(self.builder, c.LLVMIntSGT, lhs, rhs, "gttmp"), - .gte => c.LLVMBuildICmp(self.builder, c.LLVMIntSGE, lhs, rhs, "getmp"), - .and_op, .or_op => unreachable, - }; + // For vectors, dispatch based on element type; LLVM handles element-wise ops automatically + const effective_ty = if (result_type.isVector()) + result_type.vectorElementType() orelse return lhs + else + result_type; + + // Vector comparison needs special handling (returns vector of i1) + if (result_type.isVector() and (op == .eq or op == .neq)) { + return self.genVectorComparison(op, lhs, rhs, result_type, effective_ty); } + + const b = self.builder; + const is_float = effective_ty.isFloat(); + const is_unsigned = effective_ty.isUnsigned(); + + return switch (op) { + .add => if (is_float) c.LLVMBuildFAdd(b, lhs, rhs, "addtmp") else c.LLVMBuildAdd(b, lhs, rhs, "addtmp"), + .sub => if (is_float) c.LLVMBuildFSub(b, lhs, rhs, "subtmp") else c.LLVMBuildSub(b, lhs, rhs, "subtmp"), + .mul => if (is_float) c.LLVMBuildFMul(b, lhs, rhs, "multmp") else c.LLVMBuildMul(b, lhs, rhs, "multmp"), + .div => if (is_float) c.LLVMBuildFDiv(b, lhs, rhs, "divtmp") else if (is_unsigned) c.LLVMBuildUDiv(b, lhs, rhs, "divtmp") else c.LLVMBuildSDiv(b, lhs, rhs, "divtmp"), + .mod => if (is_float) c.LLVMBuildFRem(b, lhs, rhs, "modtmp") else if (is_unsigned) c.LLVMBuildURem(b, lhs, rhs, "modtmp") else c.LLVMBuildSRem(b, lhs, rhs, "modtmp"), + .eq => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOEQ, lhs, rhs, "eqtmp") else c.LLVMBuildICmp(b, c.LLVMIntEQ, lhs, rhs, "eqtmp"), + .neq => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealONE, lhs, rhs, "neqtmp") else c.LLVMBuildICmp(b, c.LLVMIntNE, lhs, rhs, "neqtmp"), + .lt => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOLT, lhs, rhs, "lttmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntULT, lhs, rhs, "lttmp") else c.LLVMBuildICmp(b, c.LLVMIntSLT, lhs, rhs, "lttmp"), + .lte => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOLE, lhs, rhs, "letmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntULE, lhs, rhs, "letmp") else c.LLVMBuildICmp(b, c.LLVMIntSLE, lhs, rhs, "letmp"), + .gt => if (is_float) c.LLVMBuildFCmp(b, c.LLVMRealOGT, lhs, rhs, "gttmp") else if (is_unsigned) c.LLVMBuildICmp(b, c.LLVMIntUGT, lhs, rhs, "gttmp") else c.LLVMBuildICmp(b, c.LLVMIntSGT, lhs, rhs, "gttmp"), + .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"), + .and_op, .or_op => unreachable, + }; } fn genAndOp(self: *CodeGen, binop: ast.BinaryOp) !c.LLVMValueRef { @@ -4047,10 +3841,10 @@ pub const CodeGen = struct { } fn genCall(self: *CodeGen, call_node: ast.Call) !c.LLVMValueRef { - // Handle union construction: Shape.variant(payload) if (call_node.callee.data == .field_access) { const fa = call_node.callee.data.field_access; - // Resolve the object to a type name (identifier, call, or field_access chain) + + // Union construction: Shape.variant(payload) const resolved_type: ?Type = blk: { if (fa.object.data == .identifier) { const name = self.type_aliases.get(fa.object.data.identifier.name) orelse fa.object.data.identifier.name; @@ -4066,19 +3860,15 @@ pub const CodeGen = struct { if (rty.isUnion()) { const type_name = rty.union_type; const payload_node: ?*Node = if (call_node.args.len > 0) call_node.args[0] else null; - const ul = ast.UnionLiteral{ + return self.genUnionLiteral(.{ .union_name = type_name, .variant_name = fa.field, .payload = payload_node, - }; - return self.genUnionLiteral(ul, type_name); + }, type_name); } } - } - // Handle namespaced calls: namespace.func(args) - if (call_node.callee.data == .field_access) { - const fa = call_node.callee.data.field_access; + // Namespaced call: namespace.func(args) if (fa.object.data == .identifier) { const ns_name = fa.object.data.identifier.name; if (self.namespaces.contains(ns_name)) { @@ -4086,18 +3876,13 @@ pub const CodeGen = struct { return self.genCallByName(qualified, call_node); } } - } - // UFCS: obj.method(args...) → method(obj, args...) - if (call_node.callee.data == .field_access) { - const fa = call_node.callee.data.field_access; + // UFCS: obj.method(args...) → method(obj, args...) const method_name = fa.field; - // Check if a free function with this name exists const method_z = self.allocator.dupeZ(u8, method_name) catch method_name; if (self.generic_templates.contains(method_name) or c.LLVMGetNamedFunction(self.module, method_z.ptr) != null) { - // Build new args: [obj, original_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| { @@ -4359,26 +4144,25 @@ pub const CodeGen = struct { // Normal generic call: Infer type bindings from arguments, widening across all args for the same type param 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); 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 if (param.type_expr.data == .type_expr) { const type_name = param.type_expr.data.type_expr.name; - const is_introducing = param.type_expr.data.type_expr.is_generic; // Check if this type name is a type parameter for (fd.type_params) |tp| { if (std.mem.eql(u8, tp.name, type_name)) { if (i < call_node.args.len) { - if (is_introducing) { + // Skip widening if binding was derived from a parameterized struct + if (!firm_bindings.contains(type_name)) { const arg_ty = self.inferType(call_node.args[i]); if (bindings.get(type_name)) |existing| { try bindings.put(type_name, Type.widen(existing, arg_ty)); } else { try bindings.put(type_name, arg_ty); } - } else if (!bindings.contains(type_name)) { - // Plain T reference with no prior binding — infer from arg - try bindings.put(type_name, self.inferType(call_node.args[i])); } } break; @@ -4408,6 +4192,7 @@ pub const CodeGen = struct { const gen_name = arg.data.type_expr.name; if (ai < info.type_param_types.len) { try bindings.put(gen_name, info.type_param_types[ai]); + try firm_bindings.put(gen_name, {}); } } } @@ -4450,7 +4235,7 @@ pub const CodeGen = struct { } // Generate mangled name - const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings); + const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings, null, null); // Check cache const callee_fn = if (self.generic_instances.get(mangled)) |cached| @@ -4496,36 +4281,7 @@ pub const CodeGen = struct { type_bindings: std.StringHashMap(Type), comptime_nodes: std.StringHashMap(*Node), ) !c.LLVMValueRef { - // Build mangled name including comptime values (use qualified name for namespace) - var buf = std.ArrayList(u8).empty; - try buf.appendSlice(self.allocator, qualified_name); - try buf.appendSlice(self.allocator, "__"); - for (fd.type_params, 0..) |tp, i| { - if (i > 0) try buf.append(self.allocator, '_'); - const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; - if (std.mem.eql(u8, constraint_name, "Type")) { - // Type param — include type name - if (type_bindings.get(tp.name)) |ty| { - const name = try ty.displayName(self.allocator); - try buf.appendSlice(self.allocator, name); - } - } else { - // Value param — include hash of value - if (comptime_nodes.get(tp.name)) |node| { - if (node.data == .string_literal) { - const hash = std.hash.Wyhash.hash(0, node.data.string_literal.raw); - var hash_buf: [16]u8 = undefined; - const hash_str = std.fmt.bufPrint(&hash_buf, "{x}", .{hash}) catch "0"; - try buf.appendSlice(self.allocator, hash_str); - } else if (node.data == .int_literal) { - var int_buf: [20]u8 = undefined; - const int_str = std.fmt.bufPrint(&int_buf, "{d}", .{node.data.int_literal.value}) catch "0"; - try buf.appendSlice(self.allocator, int_str); - } - } - } - } - const mangled = try buf.toOwnedSlice(self.allocator); + const mangled = try self.mangleGenericName(qualified_name, fd.type_params, type_bindings, null, comptime_nodes); // Instantiate if not cached if (!self.generic_instances.contains(mangled)) { @@ -4704,7 +4460,7 @@ pub const CodeGen = struct { } } - const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings); + const mangled = try self.mangleGenericName(fd.name, fd.type_params, bindings, null, null); const callee_fn = if (self.generic_instances.get(mangled)) |cached| cached else @@ -4812,15 +4568,44 @@ pub const CodeGen = struct { }; } - fn mangleGenericName(self: *CodeGen, base: []const u8, type_params: []const ast.StructTypeParam, bindings: std.StringHashMap(Type)) ![]const u8 { + fn mangleGenericName( + self: *CodeGen, + base: []const u8, + type_params: []const ast.StructTypeParam, + type_bindings: std.StringHashMap(Type), + val_bindings: ?std.StringHashMap(i64), + comptime_nodes: ?std.StringHashMap(*Node), + ) ![]const u8 { var buf = std.ArrayList(u8).empty; try buf.appendSlice(self.allocator, base); try buf.appendSlice(self.allocator, "__"); for (type_params, 0..) |tp, i| { if (i > 0) try buf.append(self.allocator, '_'); - if (bindings.get(tp.name)) |ty| { - const name = try ty.displayName(self.allocator); - try buf.appendSlice(self.allocator, name); + const constraint_name = if (tp.constraint.data == .type_expr) tp.constraint.data.type_expr.name else ""; + if (std.mem.eql(u8, constraint_name, "Type")) { + if (type_bindings.get(tp.name)) |ty| { + const name = try ty.displayName(self.allocator); + try buf.appendSlice(self.allocator, name); + } + } else if (comptime_nodes != null) { + if (comptime_nodes.?.get(tp.name)) |node| { + if (node.data == .string_literal) { + const hash = std.hash.Wyhash.hash(0, node.data.string_literal.raw); + var hash_buf: [16]u8 = undefined; + const hash_str = std.fmt.bufPrint(&hash_buf, "{x}", .{hash}) catch "0"; + try buf.appendSlice(self.allocator, hash_str); + } else if (node.data == .int_literal) { + var int_buf: [20]u8 = undefined; + const int_str = std.fmt.bufPrint(&int_buf, "{d}", .{node.data.int_literal.value}) catch "0"; + try buf.appendSlice(self.allocator, int_str); + } + } + } else if (val_bindings != null) { + if (val_bindings.?.get(tp.name)) |val| { + var tmp: [20]u8 = undefined; + const s = std.fmt.bufPrint(&tmp, "{d}", .{val}) catch "0"; + try buf.appendSlice(self.allocator, s); + } } } return try buf.toOwnedSlice(self.allocator); @@ -4901,12 +4686,7 @@ pub const CodeGen = struct { const elem_name = if (param.type_expr.data == .type_expr) param.type_expr.data.type_expr.name else "s32"; break :blk Type{ .slice_type = .{ .element_name = elem_name } }; } else self.resolveType(param.type_expr); - const llvm_ty = self.typeToLLVM(sx_ty); - const param_name_z = try self.allocator.dupeZ(u8, param.name); - const alloca = c.LLVMBuildAlloca(self.builder, llvm_ty, param_name_z.ptr); - const param_val = c.LLVMGetParam(function, llvm_param_idx); - _ = c.LLVMBuildStore(self.builder, param_val, alloca); - try self.named_values.put(param.name, .{ .ptr = alloca, .ty = sx_ty }); + try self.bindParam(function, param.name, sx_ty, llvm_param_idx); llvm_param_idx += 1; } @@ -5561,17 +5341,8 @@ pub const CodeGen = struct { /// Resolve a type name to its display string (null-terminated) for runtime use. fn resolveDisplayName(self: *CodeGen, name: []const u8) [:0]const u8 { - // Type aliases → follow to target - if (self.type_aliases.get(name)) |target| { - if (self.struct_types.get(target)) |info| - return self.allocator.dupeZ(u8, info.display_name orelse target) catch unreachable; - return self.allocator.dupeZ(u8, target) catch unreachable; - } - // Struct types → use display name - if (self.struct_types.get(name)) |info| - return self.allocator.dupeZ(u8, info.display_name orelse name) catch unreachable; - // Primitive / enum / anything else → use as-is - return self.allocator.dupeZ(u8, name) catch unreachable; + const display = self.resolveTypeName(name) orelse name; + return self.allocator.dupeZ(u8, display) catch unreachable; } /// Convert a type expression AST node to its source string representation. @@ -5623,20 +5394,9 @@ pub const CodeGen = struct { /// Resolve a name to a type display string, or null if not a type. fn resolveTypeName(self: *CodeGen, name: []const u8) ?[]const u8 { - // Type aliases (e.g. x := f64, Vec3 :: Vec(3, f32)) — follow alias first - if (self.type_aliases.get(name)) |target| { - // Check if target is a struct with a display name - if (self.struct_types.get(target)) |info| return info.display_name orelse target; - return target; - } - // Primitive types - if (Type.fromName(name) != null) return name; - // Struct types - if (self.struct_types.get(name)) |info| return info.display_name orelse name; - // Enum types - if (self.enum_types.get(name) != null) return name; - // Union types - if (self.union_types.get(name) != null) return name; + const resolved = self.type_aliases.get(name) orelse name; + if (self.struct_types.get(resolved)) |info| return info.display_name orelse resolved; + if (self.resolveTypeFromName(name) != null) return resolved; return null; } @@ -5651,6 +5411,16 @@ pub const CodeGen = struct { if (self.union_types.contains(name)) return .{ .union_type = name }; // Enums if (self.enum_types.contains(name)) return .{ .enum_type = name }; + // Vector display name: "Vector(N,T)" + if (name.len > 8 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') { + const inner = name[7 .. name.len - 1]; // "N,T" + if (std.mem.indexOfScalar(u8, inner, ',')) |comma| { + const n_str = inner[0..comma]; + const elem = inner[comma + 1 ..]; + const length = std.fmt.parseInt(u32, n_str, 10) catch return null; + return .{ .vector_type = .{ .element_name = elem, .length = length } }; + } + } // Type aliases if (self.type_aliases.get(name)) |target| return self.resolveTypeFromName(target); return null; diff --git a/src/comptime.zig b/src/comptime.zig index 5f23c87..609b82a 100644 --- a/src/comptime.zig +++ b/src/comptime.zig @@ -1260,10 +1260,7 @@ pub const VM = struct { try self.push(.{ .string_val = s }); }, - // Cast - .cast => { - // TODO: implement type casting - }, + .cast => {}, } } } diff --git a/src/core.zig b/src/core.zig index edd6756..0bbb955 100644 --- a/src/core.zig +++ b/src/core.zig @@ -93,18 +93,6 @@ pub const Compilation = struct { } pub fn renderErrors(self: *const Compilation) void { - for (self.diagnostics.items.items) |d| { - const level_str = switch (d.level) { - .err => "error", - .warn => "warning", - .note => "note", - }; - if (d.span) |span| { - const loc = errors.SourceLoc.compute(self.source, span.start); - std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ self.file_path, loc.line, loc.col, level_str, d.message }); - } else { - std.debug.print("{s}: {s}: {s}\n", .{ self.file_path, level_str, d.message }); - } - } + self.diagnostics.renderDebug(); } }; diff --git a/src/errors.zig b/src/errors.zig index 546a9ae..1572371 100644 --- a/src/errors.zig +++ b/src/errors.zig @@ -14,7 +14,8 @@ pub const SourceLoc = struct { pub fn compute(source: []const u8, byte_offset: u32) SourceLoc { var line: u32 = 1; var col: u32 = 1; - for (source[0..byte_offset]) |c| { + const end = @min(byte_offset, @as(u32, @intCast(source.len))); + for (source[0..end]) |c| { if (c == '\n') { line += 1; col = 1; @@ -93,4 +94,20 @@ pub const DiagnosticList = struct { } } } + + pub fn renderDebug(self: *const DiagnosticList) void { + for (self.items.items) |d| { + const level_str = switch (d.level) { + .err => "error", + .warn => "warning", + .note => "note", + }; + if (d.span) |span| { + const loc = SourceLoc.compute(self.source, span.start); + std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ self.file_name, loc.line, loc.col, level_str, d.message }); + } else { + std.debug.print("{s}: {s}: {s}\n", .{ self.file_name, level_str, d.message }); + } + } + } }; diff --git a/src/lexer.zig b/src/lexer.zig index 4bbe587..b3d6d41 100644 --- a/src/lexer.zig +++ b/src/lexer.zig @@ -50,31 +50,25 @@ pub const Lexer = struct { return self.lexString(start); } - // Directives: #import, #insert, #run + // Directives: #import, #insert, #run, #builtin if (c == '#') { - if (self.source.len >= start + 7 and std.mem.eql(u8, self.source[start .. start + 7], "#import") and - (start + 7 >= self.source.len or !isIdentContinue(self.source[start + 7]))) - { - self.index = start + 7; - return self.makeToken(.hash_import, start, self.index); - } - if (self.source.len >= start + 7 and std.mem.eql(u8, self.source[start .. start + 7], "#insert") and - (start + 7 >= self.source.len or !isIdentContinue(self.source[start + 7]))) - { - self.index = start + 7; - return self.makeToken(.hash_insert, start, self.index); - } - if (self.source.len >= start + 4 and std.mem.eql(u8, self.source[start .. start + 4], "#run") and - (start + 4 >= self.source.len or !isIdentContinue(self.source[start + 4]))) - { - self.index = start + 4; - return self.makeToken(.hash_run, start, self.index); - } - if (self.source.len >= start + 8 and std.mem.eql(u8, self.source[start .. start + 8], "#builtin") and - (start + 8 >= self.source.len or !isIdentContinue(self.source[start + 8]))) - { - self.index = start + 8; - return self.makeToken(.hash_builtin, start, self.index); + const directives = .{ + .{ "#import", Tag.hash_import }, + .{ "#insert", Tag.hash_insert }, + .{ "#run", Tag.hash_run }, + .{ "#builtin", Tag.hash_builtin }, + }; + inline for (directives) |d| { + const keyword = d[0]; + const tag = d[1]; + const len: u32 = keyword.len; + if (self.source.len >= start + len and + std.mem.eql(u8, self.source[start .. start + len], keyword) and + (start + len >= self.source.len or !isIdentContinue(self.source[start + len]))) + { + self.index = start + len; + return self.makeToken(tag, start, self.index); + } } self.index += 1; return self.makeToken(.invalid, start, self.index); diff --git a/src/lsp/server.zig b/src/lsp/server.zig index 86b9194..f97c314 100644 --- a/src/lsp/server.zig +++ b/src/lsp/server.zig @@ -99,12 +99,36 @@ pub const Server = struct { stderr.writeStreamingAll(self.io, msg) catch {}; } + const RequestContext = struct { + id_json: []const u8, + uri: []const u8, + }; + + fn extractRequest(self: *Server, id: ?std.json.Value, params: std.json.Value) !?RequestContext { + const req_id = id orelse return null; + const id_json = try lsp.valueToJson(self.allocator, req_id); + const td = jsonGet(params, "textDocument") orelse return null; + const uri = jsonStr(jsonGet(td, "uri") orelse return null) orelse return null; + return .{ .id_json = id_json, .uri = uri }; + } + + fn extractPosition(params: std.json.Value) ?struct { line: u32, character: u32 } { + const position = jsonGet(params, "position") orelse return null; + const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return null) orelse return null) orelse return null; + const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return null) orelse return null) orelse return null; + return .{ .line = line, .character = character }; + } + + fn sendResponse(self: *Server, id_json: []const u8, result_json: []const u8) !void { + const resp = try lsp.jsonRpcResponse(self.allocator, id_json, result_json); + try self.transport.writeMessage(resp); + } + fn handleInitialize(self: *Server, id: ?std.json.Value) !void { const req_id = id orelse return; const id_json = try lsp.valueToJson(self.allocator, req_id); const result_json = try lsp.initializeResultJson(self.allocator); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, result_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, result_json); } fn handleDidOpen(self: *Server, params: std.json.Value) !void { @@ -142,25 +166,17 @@ pub const Server = struct { } fn handleDefinition(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { - const req_id = id orelse return; - const id_json = try lsp.valueToJson(self.allocator, req_id); - - const td = jsonGet(params, "textDocument") orelse return; - const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; - const position = jsonGet(params, "position") orelse return; - const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; - const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + const ctx = try self.extractRequest(id, params) orelse return; + const pos = extractPosition(params) orelse return; + const id_json = ctx.id_json; + const uri = ctx.uri; const analysis = self.sema_cache.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, "null"); }; - const offset = positionToOffset(analysis.source, line, character) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + const offset = positionToOffset(analysis.source, pos.line, pos.character) orelse { + return try self.sendResponse(id_json, "null"); }; // Check if cursor is on a qualified name (e.g. "std.print" or UFCS "list.append") @@ -206,8 +222,7 @@ pub const Server = struct { .end = .{ .line = 0, .character = 0 }, }; const loc_json = try lsp.locationJson(self.allocator, target_uri, range); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, loc_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, loc_json); return; } @@ -223,30 +238,20 @@ pub const Server = struct { } } - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, "null"); } fn handleHover(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { - const req_id = id orelse return; - const id_json = try lsp.valueToJson(self.allocator, req_id); + const ctx = try self.extractRequest(id, params) orelse return; + const pos = extractPosition(params) orelse return; + const id_json = ctx.id_json; - const td = jsonGet(params, "textDocument") orelse return; - const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; - const position = jsonGet(params, "position") orelse return; - const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; - const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; - - const analysis = self.sema_cache.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + const analysis = self.sema_cache.get(ctx.uri) orelse { + return try self.sendResponse(id_json, "null"); }; - const offset = positionToOffset(analysis.source, line, character) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + const offset = positionToOffset(analysis.source, pos.line, pos.character) orelse { + return try self.sendResponse(id_json, "null"); }; // Check if cursor is on a qualified name (e.g. std.print) — source-based, no AST @@ -255,17 +260,13 @@ pub const Server = struct { if (analysis.import_map.get(qn.ns)) |import_path| { if (try self.formatNamespaceMemberHover(analysis, qn.ns, qn.member, import_path)) |hover_text| { const hover_json = try lsp.hoverJson(self.allocator, hover_text); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, hover_json); } } // Struct field hover (e.g. point.x) if (try self.formatStructFieldHover(analysis, qn.ns, qn.member)) |hover_text| { const hover_json = try lsp.hoverJson(self.allocator, hover_text); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, hover_json); } } @@ -275,9 +276,7 @@ pub const Server = struct { if (node.data == .enum_literal) { if (try self.formatEnumVariantHover(analysis, node.data.enum_literal.name)) |hover_text| { const hover_json = try lsp.hoverJson(self.allocator, hover_text); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, hover_json); } } } @@ -309,26 +308,18 @@ pub const Server = struct { const source_for_hover = if (resolved) |r| r.source else analysis.source; const hover_text = try formatSymbolHover(self.allocator, sym, analysis.root, source_for_hover); const hover_json = try lsp.hoverJson(self.allocator, hover_text); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, hover_json); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, hover_json); } - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, "null"); } fn handleDocumentSymbol(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { - const req_id = id orelse return; - const id_json = try lsp.valueToJson(self.allocator, req_id); + const ctx = try self.extractRequest(id, params) orelse return; + const id_json = ctx.id_json; - const td = jsonGet(params, "textDocument") orelse return; - const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; - - const analysis = self.sema_cache.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "[]"); - try self.transport.writeMessage(resp); - return; + const analysis = self.sema_cache.get(ctx.uri) orelse { + return try self.sendResponse(id_json, "[]"); }; var doc_symbols = std.ArrayList(lsp.DocumentSymbol).empty; @@ -359,23 +350,18 @@ pub const Server = struct { } const symbols_json = try lsp.documentSymbolsJson(self.allocator, doc_symbols.items); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, symbols_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, symbols_json); } fn handleCompletion(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { - const req_id = id orelse return; - const id_json = try lsp.valueToJson(self.allocator, req_id); - - const td = jsonGet(params, "textDocument") orelse return; - const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; - const position = jsonGet(params, "position") orelse return; - const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; - const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + const ctx = try self.extractRequest(id, params) orelse return; + const pos = extractPosition(params) orelse return; + const id_json = ctx.id_json; + const uri = ctx.uri; // Check if cursor is after a dot (possibly with partial identifier typed) if (self.documents.get(uri)) |doc| { - if (positionToOffset(doc.text, line, character)) |off| { + if (positionToOffset(doc.text, pos.line, pos.character)) |off| { // Scan backwards past any identifier characters to find a dot var scan = off; while (scan > 0 and isIdentChar(doc.text[scan - 1])) scan -= 1; @@ -388,9 +374,7 @@ pub const Server = struct { // Regular completion: all in-scope symbols + keywords const analysis = self.sema_cache.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "[]"); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, "[]"); }; var items = std.ArrayList(lsp.CompletionItem).empty; @@ -449,8 +433,7 @@ pub const Server = struct { } const items_json = try lsp.completionItemsJson(self.allocator, items.items); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, items_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, items_json); } fn handleDotCompletion(self: *Server, id_json: []const u8, uri: []const u8, text: []const u8, cursor_offset: u32) !void { @@ -475,8 +458,7 @@ pub const Server = struct { } const items_json = try lsp.completionListJson(self.allocator, items.items); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, items_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, items_json); } fn collectMemberCompletions(self: *Server, items: *std.ArrayList(lsp.CompletionItem), analysis: DocumentAnalysis, name: []const u8) !void { @@ -594,31 +576,21 @@ pub const Server = struct { } fn handleSignatureHelp(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { - const req_id = id orelse return; - const id_json = try lsp.valueToJson(self.allocator, req_id); - - const td = jsonGet(params, "textDocument") orelse return; - const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; - const position = jsonGet(params, "position") orelse return; - const line = std.math.cast(u32, jsonInt(jsonGet(position, "line") orelse return) orelse return) orelse return; - const character = std.math.cast(u32, jsonInt(jsonGet(position, "character") orelse return) orelse return) orelse return; + const req = try self.extractRequest(id, params) orelse return; + const pos = extractPosition(params) orelse return; + const id_json = req.id_json; + const uri = req.uri; const doc = self.documents.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, "null"); }; - const offset = positionToOffset(doc.text, line, character) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + const offset = positionToOffset(doc.text, pos.line, pos.character) orelse { + return try self.sendResponse(id_json, "null"); }; - const ctx = findCallContext(doc.text, offset) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + const call_ctx = findCallContext(doc.text, offset) orelse { + return try self.sendResponse(id_json, "null"); }; // Built-in function signatures @@ -636,25 +608,21 @@ pub const Server = struct { .{ .name = "write", .label = "write(str: string) -> void", .params = &.{"str: string"} }, }; for (&builtin_sigs) |b| { - const matches = std.mem.eql(u8, ctx.name, b.name) or - (std.mem.startsWith(u8, ctx.name, "std.") and std.mem.eql(u8, ctx.name[4..], b.name)); + const matches = std.mem.eql(u8, call_ctx.name, b.name) or + (std.mem.startsWith(u8, call_ctx.name, "std.") and std.mem.eql(u8, call_ctx.name[4..], b.name)); if (matches) { - const sig_json = try lsp.signatureHelpJson(self.allocator, b.label, b.params, ctx.active_param); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, sig_json); - try self.transport.writeMessage(resp); - return; + const sig_json = try lsp.signatureHelpJson(self.allocator, b.label, b.params, call_ctx.active_param); + return try self.sendResponse(id_json, sig_json); } } // Look up function in sema cache const analysis = self.sema_cache.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); - return; + return try self.sendResponse(id_json, "null"); }; // Try to find the function — either at top level or inside a namespace - const fn_node = findFnDeclByName(analysis, ctx.name); + const fn_node = findFnDeclByName(analysis, call_ctx.name); if (fn_node) |fd| { var label_buf = std.ArrayList(u8).empty; @@ -684,27 +652,19 @@ pub const Server = struct { } } - const sig_json = try lsp.signatureHelpJson(self.allocator, label_buf.items, param_labels.items, ctx.active_param); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, sig_json); - try self.transport.writeMessage(resp); - return; + const sig_json = try lsp.signatureHelpJson(self.allocator, label_buf.items, param_labels.items, call_ctx.active_param); + return try self.sendResponse(id_json, sig_json); } - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "null"); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, "null"); } fn handleSemanticTokens(self: *Server, id: ?std.json.Value, params: std.json.Value) !void { - const req_id = id orelse return; - const id_json = try lsp.valueToJson(self.allocator, req_id); + const ctx = try self.extractRequest(id, params) orelse return; + const id_json = ctx.id_json; - const td = jsonGet(params, "textDocument") orelse return; - const uri = jsonStr(jsonGet(td, "uri") orelse return) orelse return; - - const analysis = self.sema_cache.get(uri) orelse { - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, "{\"data\":[]}"); - try self.transport.writeMessage(resp); - return; + const analysis = self.sema_cache.get(ctx.uri) orelse { + return try self.sendResponse(id_json, "{\"data\":[]}"); }; var data = std.ArrayList(u32).empty; @@ -727,8 +687,7 @@ pub const Server = struct { } const result_json = try lsp.semanticTokensJson(self.allocator, data.items); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, result_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, result_json); } fn classifyToken(tok: sx.token.Token, analysis: DocumentAnalysis) ?u32 { @@ -1308,8 +1267,7 @@ pub const Server = struct { else uri; const loc_json = try lsp.locationJson(self.allocator, target_uri, range); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, loc_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, loc_json); return true; } @@ -1355,8 +1313,7 @@ pub const Server = struct { const range = spanToRange(imp_source, target.span); const target_uri = try std.fmt.allocPrint(self.allocator, "file://{s}", .{import_path}); const loc_json = try lsp.locationJson(self.allocator, target_uri, range); - const resp = try lsp.jsonRpcResponse(self.allocator, id_json, loc_json); - try self.transport.writeMessage(resp); + try self.sendResponse(id_json, loc_json); return; } diff --git a/src/main.zig b/src/main.zig index caea656..0093c95 100644 --- a/src/main.zig +++ b/src/main.zig @@ -115,11 +115,11 @@ fn readSource(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) return try allocator.dupeZ(u8, source_bytes); } -fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !void { +fn compilePipeline(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !sx.core.Compilation { const source = try readSource(allocator, io, input_path); var comp = sx.core.Compilation.init(allocator, io, input_path, source); - defer comp.deinit(); + errdefer comp.deinit(); comp.parse() catch { comp.renderErrors(); return error.CompileError; }; comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; @@ -127,21 +127,21 @@ fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !voi var cg = &comp.cg.?; cg.verify() catch { comp.renderErrors(); return error.CompileError; }; - cg.printIR(); + + return comp; +} + +fn emitIR(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8) !void { + var comp = try compilePipeline(allocator, io, input_path); + defer comp.deinit(); + comp.cg.?.printIR(); } fn compile(allocator: std.mem.Allocator, io: std.Io, input_path: []const u8, output_path: []const u8) !void { - const source = try readSource(allocator, io, input_path); - - var comp = sx.core.Compilation.init(allocator, io, input_path, source); + var comp = try compilePipeline(allocator, io, input_path); defer comp.deinit(); - comp.parse() catch { comp.renderErrors(); return error.CompileError; }; - comp.resolveImports() catch { comp.renderErrors(); return error.CompileError; }; - comp.generateCode() catch { comp.renderErrors(); return error.CompileError; }; - var cg = &comp.cg.?; - cg.verify() catch { comp.renderErrors(); return error.CompileError; }; // Emit object file const obj_path = try std.fmt.allocPrintSentinel(allocator, "{s}.o", .{output_path}, 0); diff --git a/src/parser.zig b/src/parser.zig index 3bac7b8..d7b1d80 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -169,6 +169,16 @@ pub const Parser = struct { // Otherwise it's a constant expression const value = try self.parseExpr(); + + // name :: type_expr #builtin; — builtin with type annotation + if (self.current.tag == .hash_builtin) { + const bi_start = self.current.loc.start; + self.advance(); + try self.expect(.semicolon); + const bi = try self.createNode(bi_start, .{ .builtin_expr = {} }); + return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = value, .value = bi } }); + } + try self.expect(.semicolon); return try self.createNode(start_pos, .{ .const_decl = .{ .name = name, .type_annotation = null, .value = value } }); } diff --git a/src/sema.zig b/src/sema.zig index d6452ae..eaa5cd4 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -245,10 +245,8 @@ pub const Analyzer = struct { const elem_name = elem_type.displayName(self.allocator) catch return .void_type; return .{ .many_pointer_type = .{ .element_name = elem_name } }; } - // Parameterized type: Vector(N, T) or generic struct + // Sema does not resolve generics; codegen handles instantiation if (tn.data == .parameterized_type_expr) { - // For now, skip generic instantiation — just return void_type - // (will be extended when generic support is added to sema) return .void_type; } // type_expr or identifier — check aliases, enums, structs