diff --git a/examples/09-import.sx b/examples/09-import.sx index 7ca61db..282b135 100644 --- a/examples/09-import.sx +++ b/examples/09-import.sx @@ -1,7 +1,7 @@ std :: #import "modules/std.sx"; //flat -#import "modules/math.sx"; +#import "modules/math"; main :: () -> s32 { { diff --git a/examples/11-vector-math.sx b/examples/11-vector-math.sx index cbc0f9e..ee3c2e5 100644 --- a/examples/11-vector-math.sx +++ b/examples/11-vector-math.sx @@ -1,5 +1,5 @@ #import "modules/std.sx"; -math :: #import "modules/math.sx"; +math :: #import "modules/math"; vec3 :: (x:f32, y:f32, z:f32) -> Vector(3,f32) { .[x, y, z]; diff --git a/examples/12-meta.sx b/examples/12-meta.sx index 699ab61..b0be483 100644 --- a/examples/12-meta.sx +++ b/examples/12-meta.sx @@ -1,5 +1,5 @@ #import "modules/std.sx"; -#import "modules/math.sx"; +#import "modules/math"; test :: () -> s32 { 0; diff --git a/examples/issue-01.sx b/examples/issue-01.sx new file mode 100644 index 0000000..074626d --- /dev/null +++ b/examples/issue-01.sx @@ -0,0 +1,41 @@ +#import "modules/std.sx"; + +// Forward references: types, struct fields, methods, and free functions +// can reference types declared later in the file. + +// Free function referencing types declared later +make_frame :: (e: EdgeInsets) -> Frame { + Frame.{ x = e.left, y = e.top, + w = 100.0 - e.left - e.right, + h = 100.0 - e.top - e.bottom }; +} + +// Struct with a field whose type is declared later +Container :: struct { + frame: Frame; + insets: EdgeInsets; +} + +Frame :: struct { + x, y, w, h: f32; + + inset :: (self: Frame, insets: EdgeInsets) -> Frame { + Frame.{ x = self.x + insets.left, y = self.y + insets.top, + w = self.w - insets.left - insets.right, + h = self.h - insets.top - insets.bottom }; + } +} + +EdgeInsets :: struct { + top, left, bottom, right: f32; +} + +main :: () { + e := EdgeInsets.{ top = 10.0, left = 10.0, bottom = 10.0, right = 10.0 }; + f := make_frame(e); + r := f.inset(e); + c := Container.{ frame = f, insets = e }; + print("{}", r.x); + print(" {}", r.w); + print(" {}", c.frame.x); +} diff --git a/examples/modules/math.sx b/examples/modules/math.sx deleted file mode 100644 index 8266786..0000000 --- a/examples/modules/math.sx +++ /dev/null @@ -1,17 +0,0 @@ -#import "std.sx"; - -dot :: (a: Vector(3,f32), b: Vector(3,f32)) -> f32 { - return a.x*b.x + a.y*b.y + a.z*b.z; -} - -cross :: (a: Vector(3,f32), b: Vector(3,f32)) -> Vector(3,f32) { - .[a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x]; -} - -length :: (v: Vector(3,f32)) -> f32 { - return sqrt(dot(v, v)); -} - -normalize :: (v: Vector(3,f32)) -> Vector(3,f32) { - return v / length(v); -} \ No newline at end of file diff --git a/examples/modules/math/math.sx b/examples/modules/math/math.sx index c341b0f..6cb81d2 100644 --- a/examples/modules/math/math.sx +++ b/examples/modules/math/math.sx @@ -1,5 +1,37 @@ PI :f32: 3.14159265; +TAU :f32: 6.28318530; +DEG2RAD :f32: 0.01745329; +RAD2DEG :f32: 57.2957795; sqrt :: (x: $T) -> T #builtin; sin :: (x: $T) -> T #builtin; cos :: (x: $T) -> T #builtin; +floor :: (x: $T) -> T #builtin; + +min :: (a: $T, b: T) -> T { + if a < b then a else b; +} + +max :: (a: $T, b: T) -> T { + if a > b then a else b; +} + +clamp :: (val: $T, lo: T, hi: T) -> T { + if val < lo then lo + else if val > hi then hi + else val; +} + +abs :: (x: $T) -> T { + if x < 0 then 0 - x else x; +} + +lerp :: (a: f32, b: f32, t: f32) -> f32 { + a + (b - a) * t; +} + +sign :: (x: $T) -> T { + if x > 0 then 1 + else if x < 0 then 0 - 1 + else 0; +} diff --git a/examples/modules/math/matrix44.sx b/examples/modules/math/matrix44.sx new file mode 100644 index 0000000..c0c60c7 --- /dev/null +++ b/examples/modules/math/matrix44.sx @@ -0,0 +1,117 @@ +// Column-major 4x4 float matrix (OpenGL convention) +// data[col * 4 + row] + +Mat4 :: struct { + data: [16]f32; + + identity :: () -> Mat4 { + Mat4.{ data = .[ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ]}; + } + + zero :: () -> Mat4 { + Mat4.{ data = .[ + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0, + 0.0, 0.0, 0.0, 0.0 + ]}; + } + + mul :: (self: Mat4, b: Mat4) -> Mat4 { + r := Mat4.zero(); + col := 0; + while col < 4 { + row := 0; + while row < 4 { + sum : f32 = 0.0; + k := 0; + while k < 4 { + sum = sum + self.data[k * 4 + row] * b.data[col * 4 + k]; + k += 1; + } + r.data[col * 4 + row] = sum; + row += 1; + } + col += 1; + } + r; + } + + translate :: (x: f32, y: f32, z: f32) -> Mat4 { + m := Mat4.identity(); + m.data[12] = x; + m.data[13] = y; + m.data[14] = z; + m; + } + + scale :: (x: f32, y: f32, z: f32) -> Mat4 { + m := Mat4.zero(); + m.data[0] = x; + m.data[5] = y; + m.data[10] = z; + m.data[15] = 1.0; + m; + } + + rotate_x :: (angle: f32) -> Mat4 { + c := cos(angle); + s := sin(angle); + m := Mat4.identity(); + m.data[5] = c; + m.data[6] = s; + m.data[9] = 0.0 - s; + m.data[10] = c; + m; + } + + rotate_y :: (angle: f32) -> Mat4 { + c := cos(angle); + s := sin(angle); + m := Mat4.identity(); + m.data[0] = c; + m.data[2] = 0.0 - s; + m.data[8] = s; + m.data[10] = c; + m; + } + + rotate_z :: (angle: f32) -> Mat4 { + c := cos(angle); + s := sin(angle); + m := Mat4.identity(); + m.data[0] = c; + m.data[1] = s; + m.data[4] = 0.0 - s; + m.data[5] = c; + m; + } + + ortho :: (left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Mat4 { + m := Mat4.zero(); + m.data[0] = 2.0 / (right - left); + m.data[5] = 2.0 / (top - bottom); + m.data[10] = 0.0 - 2.0 / (far - near); + m.data[12] = 0.0 - (right + left) / (right - left); + m.data[13] = 0.0 - (top + bottom) / (top - bottom); + m.data[14] = 0.0 - (far + near) / (far - near); + m.data[15] = 1.0; + m; + } + + perspective :: (fov: f32, aspect: f32, near: f32, far: f32) -> Mat4 { + half_tan := sin(fov * 0.5) / cos(fov * 0.5); + m := Mat4.zero(); + m.data[0] = 1.0 / (aspect * half_tan); + m.data[5] = 1.0 / half_tan; + m.data[10] = 0.0 - (far + near) / (far - near); + m.data[11] = 0.0 - 1.0; + m.data[14] = 0.0 - 2.0 * far * near / (far - near); + m; + } +} diff --git a/examples/modules/math/vector2.sx b/examples/modules/math/vector2.sx new file mode 100644 index 0000000..86ac9f3 --- /dev/null +++ b/examples/modules/math/vector2.sx @@ -0,0 +1,49 @@ +Vec2 :: struct { + x, y: f32; + + zero :: () -> Vec2 { Vec2.{ x = 0.0, y = 0.0 }; } + + add :: (self: Vec2, b: Vec2) -> Vec2 { + Vec2.{ x = self.x + b.x, y = self.y + b.y }; + } + + sub :: (self: Vec2, b: Vec2) -> Vec2 { + Vec2.{ x = self.x - b.x, y = self.y - b.y }; + } + + scale :: (self: Vec2, s: f32) -> Vec2 { + Vec2.{ x = self.x * s, y = self.y * s }; + } + + dot :: (self: Vec2, b: Vec2) -> f32 { + self.x * b.x + self.y * b.y; + } + + length :: (self: Vec2) -> f32 { + sqrt(self.x * self.x + self.y * self.y); + } + + normalize :: (self: Vec2) -> Vec2 { + len := self.length(); + if len > 0.0 { + return Vec2.{ x = self.x / len, y = self.y / len }; + } + Vec2.zero(); + } + + lerp :: (self: Vec2, b: Vec2, t: f32) -> Vec2 { + Vec2.{ x = self.x + (b.x - self.x) * t, y = self.y + (b.y - self.y) * t }; + } + + distance :: (self: Vec2, b: Vec2) -> f32 { + self.sub(b).length(); + } + + negate :: (self: Vec2) -> Vec2 { + Vec2.{ x = 0.0 - self.x, y = 0.0 - self.y }; + } + + equals :: (self: Vec2, b: Vec2) -> bool { + self.x == b.x and self.y == b.y; + } +} diff --git a/src/codegen.zig b/src/codegen.zig index 4fafb43..f611421 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -1239,13 +1239,27 @@ pub const CodeGen = struct { pub fn generate(self: *CodeGen, root: *Node) !void { if (root.data != .root) return self.emitError("expected root node for code generation"); - // Store root decls for VM on-demand function compilation self.root_decls = root.data.root.decls; - // Initialize built-in function declarations (printf, etc.) self.builtins = Builtins.init(self.module, self.context); + const decls = root.data.root.decls; - // Pre-scan: collect named library constants (handles forward references) - for (root.data.root.decls) |decl| { + try self.collectLibraries(decls); + + // Each phase depends on the previous: types must exist before fields can + // reference them, fields must be resolved before function signatures can + // use them as parameter/return types, and all signatures must be registered + // before emitting function bodies. + try self.registerTypes(decls); + try self.resolveFields(decls); + try self.registerFunctions(decls); + try self.generateBodies(decls); + try self.generateDeferred(); + } + + // ── Phase 0: collect library constants ────────────────────────────── + + fn collectLibraries(self: *CodeGen, decls: []const *Node) !void { + for (decls) |decl| { self.current_source_file = decl.source_file; switch (decl.data) { .library_decl => |ld| { @@ -1253,33 +1267,29 @@ pub const CodeGen = struct { }, .namespace_decl => |ns| { for (ns.decls) |nd| { - switch (nd.data) { - .library_decl => |nld| { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, nld.name }); - try self.library_constants.put(qualified, nld.lib_name); - }, - else => {}, + if (nd.data == .library_decl) { + const nld = nd.data.library_decl; + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, nld.name }); + try self.library_constants.put(qualified, nld.lib_name); } } }, else => {}, } } + } - // Pass 1: Register all declarations (signatures only, no bodies) - for (root.data.root.decls) |decl| { + // ── Phase 1: register type names (no field resolution yet) ────────── + + fn registerTypes(self: *CodeGen, decls: []const *Node) !void { + for (decls) |decl| { self.current_source_file = decl.source_file; switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr) { try self.builtin_functions.put(fd.name, {}); - } else if (fd.body.data == .foreign_expr) { - // External C function — register LLVM declaration (no body) - try self.registerFnDecl(fd, fd.name); } else if (fd.type_params.len > 0) { try self.generic_templates.put(fd.name, fd); - } else { - try self.registerFnDecl(fd, fd.name); } try self.fn_signatures.put(fd.name, self.buildFnSignature(fd)); }, @@ -1288,68 +1298,48 @@ pub const CodeGen = struct { }, .enum_decl => |ed| { if (ed.variant_types.len > 0) { - // Tagged enum with payloads - try self.registerTaggedEnum(ed); + try self.registerTaggedEnumName(ed); } else { - // Payload-less enum try self.type_registry.put(ed.name, .{ .plain_enum = ed.variant_names }); _ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name }); - - if (ed.is_flags) { - try self.flags_enum_types.put(ed.name, {}); - } - - // Register backing type if specified - if (ed.backing_type) |bt_node| { - const bt = self.resolveType(bt_node); - try self.enum_backing_types.put(ed.name, self.typeToLLVM(bt)); - } - - // Compute and store variant values + if (ed.is_flags) try self.flags_enum_types.put(ed.name, {}); + // enum variant values (no resolveType needed) const values = try self.allocator.alloc(i64, ed.variant_names.len); for (ed.variant_names, 0..) |_, i| { if (ed.variant_values.len > i and ed.variant_values[i] != null) { - // Explicit value: evaluate comptime int literal const val_node = ed.variant_values[i].?; values[i] = switch (val_node.data) { .int_literal => |il| il.value, - else => @as(i64, @intCast(i)), // fallback + else => @as(i64, @intCast(i)), }; } else if (ed.is_flags) { - // Auto power-of-2: 1, 2, 4, 8, ... values[i] = @as(i64, 1) << @intCast(i); } else { - // Regular enum: sequential 0, 1, 2, ... values[i] = @intCast(i); } } try self.enum_variant_values.put(ed.name, values); } }, - .struct_decl => |sd| try self.registerStructType(sd), - .union_decl => |ud| try self.registerUnionType(ud), + .struct_decl => |sd| try self.registerStructName(sd), + .union_decl => |ud| try self.registerUnionName(ud), .const_decl => |cd| { if (cd.value.data == .builtin_expr) { - // #builtin constant — skip codegen - } else if (cd.value.data == .lambda) { - try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); + // skip } else if (cd.value.data == .type_expr) { try self.type_registry.put(cd.name, .{ .alias = cd.value.data.type_expr.name }); } else if (cd.value.data == .call) { - // Check if this is a generic struct or type function instantiation const callee_name = if (cd.value.data.call.callee.data == .identifier) cd.value.data.call.callee.data.identifier.name else null; if (callee_name) |cn| { if (self.generic_struct_templates.get(cn)) |tmpl| { - // Generic struct instantiation: Vec3 :: Vec(3, f32); const result_ty = try self.instantiateGenericStruct(cn, tmpl, cd.value.data.call.args); if (result_ty.isStruct()) { try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type }); } } else if (self.generic_templates.get(cn)) |tmpl| { - // Type-returning function: Foo :: Complex(u32); const result_ty = try self.instantiateTypeFunction(cd.name, cn, tmpl, cd.value.data.call.args); if (result_ty.isStruct()) { try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type }); @@ -1357,11 +1347,74 @@ pub const CodeGen = struct { try self.type_registry.put(cd.name, .{ .alias = result_ty.union_type }); } } else if (self.builtin_functions.contains(cn)) { - // Builtin type function (e.g., Vector(4, f32), Array(5, s32)) if (self.resolveBuiltinType(cn, cd.value.data.call.args)) |result_ty| { const display = try result_ty.displayName(self.allocator); try self.type_registry.put(cd.name, .{ .alias = display }); - } else { + } + } + } + } + }, + .namespace_decl => |ns| try self.registerNamespaceTypes(ns), + .protocol_decl => |pd| try self.registerProtocolDecl(pd), + else => {}, + } + } + } + + // ── Phase 2: resolve struct/union/enum field types ────────────────── + + fn resolveFields(self: *CodeGen, decls: []const *Node) !void { + for (decls) |decl| { + self.current_source_file = decl.source_file; + switch (decl.data) { + .enum_decl => |ed| { + if (ed.variant_types.len > 0) { + try self.resolveTaggedEnumFields(ed); + } else if (ed.backing_type) |bt_node| { + const bt = self.resolveType(bt_node); + try self.enum_backing_types.put(ed.name, self.typeToLLVM(bt)); + } + }, + .struct_decl => |sd| try self.resolveStructFields(sd), + .union_decl => |ud| try self.resolveUnionFields(ud), + .namespace_decl => |ns| try self.resolveNamespaceFields(ns), + else => {}, + } + } + } + + // ── Phase 3: register function signatures ─────────────────────────── + + fn registerFunctions(self: *CodeGen, decls: []const *Node) !void { + for (decls) |decl| { + self.current_source_file = decl.source_file; + switch (decl.data) { + .fn_decl => |fd| { + if (fd.body.data != .builtin_expr and fd.type_params.len == 0) { + try self.registerFnDecl(fd, fd.name); + } + }, + .struct_decl => |sd| try self.registerStructMethods(sd), + .const_decl => |cd| { + if (cd.value.data == .builtin_expr or cd.value.data == .type_expr) { + // already handled + } else if (cd.value.data == .lambda) { + try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); + } else if (cd.value.data == .call) { + const callee_name = if (cd.value.data.call.callee.data == .identifier) + cd.value.data.call.callee.data.identifier.name + else + null; + if (callee_name) |cn| { + if (self.generic_struct_templates.contains(cn)) { + // type instantiation, already handled + } else if (self.generic_templates.contains(cn)) { + if (!self.type_registry.contains(cd.name)) { + try self.registerTopLevelConstant(cd); + } + } else if (self.builtin_functions.contains(cn)) { + if (!self.type_registry.contains(cd.name)) { try self.registerTopLevelConstant(cd); } } else { @@ -1371,45 +1424,33 @@ pub const CodeGen = struct { try self.registerTopLevelConstant(cd); } } else if (cd.value.data == .comptime_expr) { - // Use explicit type annotation if available const ct_type_override: ?Type = if (cd.type_annotation) |te| Type.fromTypeExpr(te) else null; try self.registerComptimeGlobal(cd.name, cd.value.data.comptime_expr.expr, ct_type_override); } else { - // Top-level value constant (e.g., SPECIAL_VALUE :u8: 42;) try self.registerTopLevelConstant(cd); } }, .comptime_expr => |ct| { try self.comptime_side_effects.append(self.allocator, ct.expr); }, - .namespace_decl => |ns| { - try self.registerNamespace(ns); - }, - .var_decl => |vd| { - try self.registerGlobalVar(vd); - }, - .ufcs_alias => |ua| { - try self.ufcs_aliases.put(ua.name, ua.target); - }, - .protocol_decl => |pd| { - try self.registerProtocolDecl(pd); - }, - .impl_block => |ib| { - try self.registerImplBlock(ib); - }, + .namespace_decl => |ns| try self.registerNamespaceFunctions(ns), + .var_decl => |vd| try self.registerGlobalVar(vd), + .ufcs_alias => |ua| try self.ufcs_aliases.put(ua.name, ua.target), + .impl_block => |ib| try self.registerImplBlock(ib), else => {}, } } + } - // Pass 2: Generate all function bodies - // Functions with Any parameters (like any_to_string) are deferred to Pass 3 - // so that all types are registered before their type-match expressions are compiled. - for (root.data.root.decls) |decl| { + // ── Phase 4: generate function bodies ─────────────────────────────── + + fn generateBodies(self: *CodeGen, decls: []const *Node) !void { + for (decls) |decl| { self.current_source_file = decl.source_file; switch (decl.data) { .fn_decl => |fd| { if (fd.body.data == .builtin_expr or fd.body.data == .foreign_expr) { - // skip — no body to generate + // no body to generate } else if (fd.type_params.len == 0) { if (shouldDeferFnBody(fd)) { try self.deferred_fn_bodies.append(self.allocator, .{ .fd = fd, .name = fd.name, .source_file = self.current_source_file }); @@ -1425,24 +1466,22 @@ pub const CodeGen = struct { }, .namespace_decl => |ns| { try self.genNamespaceBodies(ns); - // Generate struct method bodies within namespace for (ns.decls) |nd| { if (nd.data == .struct_decl) { try self.genStructMethodBodies(nd.data.struct_decl); } } }, - .struct_decl => |sd| { - try self.genStructMethodBodies(sd); - }, - .impl_block => |ib| { - try self.genImplMethodBodies(ib); - }, + .struct_decl => |sd| try self.genStructMethodBodies(sd), + .impl_block => |ib| try self.genImplMethodBodies(ib), else => {}, } } + } - // Pass 3: Compile deferred function bodies (after all types are registered) + // ── Phase 5: deferred bodies + comptime side effects ──────────────── + + fn generateDeferred(self: *CodeGen) !void { for (self.deferred_fn_bodies.items) |deferred| { const saved_ns = self.current_namespace; const saved_sf = self.current_source_file; @@ -1453,7 +1492,6 @@ pub const CodeGen = struct { try self.genFnBody(deferred.fd, deferred.name); } - // Execute comptime side effects via bytecode VM (e.g., #run main();) for (self.comptime_side_effects.items) |expr| { _ = try self.comptimeEval(expr, .void_type); } @@ -2358,8 +2396,73 @@ pub const CodeGen = struct { } } - fn registerNamespace(self: *CodeGen, ns: ast.NamespaceDecl) !void { + /// registerTypes helper: register type names within a namespace. + fn registerNamespaceTypes(self: *CodeGen, ns: ast.NamespaceDecl) !void { try self.namespaces.put(ns.name, {}); + for (ns.decls) |decl| { + switch (decl.data) { + .enum_decl => |ed| { + if (ed.variant_types.len > 0) { + try self.registerTaggedEnumName(ed); + const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); + try self.type_registry.put(qualified_u, .{ .alias = ed.name }); + } else { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); + try self.type_registry.put(qualified, .{ .plain_enum = ed.variant_names }); + _ = try self.getAnyTypeId(qualified, .{ .enum_type = qualified }); + } + }, + .struct_decl => |sd| { + try self.registerStructName(sd); + const qualified_s = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, sd.name }); + try self.type_registry.put(qualified_s, .{ .alias = sd.name }); + }, + .union_decl => |ud| { + try self.registerUnionName(ud); + const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ud.name }); + try self.type_registry.put(qualified_u, .{ .alias = ud.name }); + }, + .const_decl => |cd| { + if (cd.value.data == .type_expr) { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); + try self.type_registry.put(qualified, .{ .alias = cd.value.data.type_expr.name }); + } + }, + .library_decl => |ld| { + try self.foreign_libraries.append(self.allocator, ld.lib_name); + }, + .protocol_decl => |pd| { + try self.registerProtocolDecl(pd); + }, + else => {}, + } + } + } + + /// resolveFields helper: resolve field types within a namespace. + fn resolveNamespaceFields(self: *CodeGen, ns: ast.NamespaceDecl) !void { + for (ns.decls) |decl| { + switch (decl.data) { + .enum_decl => |ed| { + if (ed.variant_types.len > 0) { + try self.resolveTaggedEnumFields(ed); + } else { + if (ed.backing_type) |bt_node| { + const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); + const bt = self.resolveType(bt_node); + try self.enum_backing_types.put(qualified, self.typeToLLVM(bt)); + } + } + }, + .struct_decl => |sd| try self.resolveStructFields(sd), + .union_decl => |ud| try self.resolveUnionFields(ud), + else => {}, + } + } + } + + /// registerFunctions helper: register function signatures within a namespace. + fn registerNamespaceFunctions(self: *CodeGen, ns: ast.NamespaceDecl) !void { for (ns.decls) |decl| { switch (decl.data) { .fn_decl => |fd| { @@ -2370,18 +2473,14 @@ pub const CodeGen = struct { } const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, fd.name }); if (fd.body.data == .foreign_expr) { - // External C function in namespace — register LLVM declaration try self.registerFnDecl(fd, fd.name); - // Also track qualified name as foreign for ABI lowering at call sites try self.foreign_fns.put(qualified, {}); - // Track qualified rename mapping if C name differs const fe = fd.body.data.foreign_expr; if (fe.c_name) |c_name| { if (!std.mem.eql(u8, c_name, fd.name)) { try self.foreign_name_map.put(qualified, c_name); } } - // Store param types under qualified name so call-site type resolution works var param_types = std.ArrayList(Type).empty; for (fd.params) |param| { if (param.is_comptime) continue; @@ -2394,32 +2493,8 @@ pub const CodeGen = struct { try self.registerFnDecl(fd, qualified); } }, - .enum_decl => |ed| { - if (ed.variant_types.len > 0) { - // Tagged enum with payloads - try self.registerTaggedEnum(ed); - const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); - try self.type_registry.put(qualified_u, .{ .alias = ed.name }); - } else { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ed.name }); - try self.type_registry.put(qualified, .{ .plain_enum = ed.variant_names }); - _ = try self.getAnyTypeId(qualified, .{ .enum_type = qualified }); - if (ed.backing_type) |bt_node| { - const bt = self.resolveType(bt_node); - try self.enum_backing_types.put(qualified, self.typeToLLVM(bt)); - } - } - }, .struct_decl => |sd| { - try self.registerStructType(sd); - // Register qualified alias so rl.Color resolves to Color - const qualified_s = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, sd.name }); - try self.type_registry.put(qualified_s, .{ .alias = sd.name }); - }, - .union_decl => |ud| { - try self.registerUnionType(ud); - const qualified_u = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, ud.name }); - try self.type_registry.put(qualified_u, .{ .alias = ud.name }); + try self.registerStructMethods(sd); }, .const_decl => |cd| { if (cd.value.data == .builtin_expr) { @@ -2427,13 +2502,8 @@ pub const CodeGen = struct { } else if (cd.value.data == .lambda) { const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); try self.registerLambdaAsFunction(qualified, cd.value.data.lambda); - } else if (cd.value.data == .type_expr) { - const qualified = try std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns.name, cd.name }); - try self.type_registry.put(qualified, .{ .alias = cd.value.data.type_expr.name }); } - }, - .library_decl => |ld| { - try self.foreign_libraries.append(self.allocator, ld.lib_name); + // type aliases already handled in registerNamespaceTypes }, .var_decl => |vd| { try self.registerGlobalVar(vd); @@ -2441,8 +2511,8 @@ pub const CodeGen = struct { .ufcs_alias => |ua| { try self.ufcs_aliases.put(ua.name, ua.target); }, - .protocol_decl => |pd| { - try self.registerProtocolDecl(pd); + .protocol_decl => { + // already registered in registerNamespaceTypes }, .impl_block => |ib| { try self.registerImplBlock(ib); @@ -4503,6 +4573,10 @@ pub const CodeGen = struct { }; fn buildStructFields(self: *CodeGen, name: []const u8, field_type_nodes: []const *Node) !StructBuildResult { + return self.buildStructFieldsWithType(name, field_type_nodes, null); + } + + fn buildStructFieldsWithType(self: *CodeGen, name: []const u8, field_type_nodes: []const *Node, existing_llvm_type: ?c.LLVMTypeRef) !StructBuildResult { var field_sx_types = std.ArrayList(Type).empty; var field_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; @@ -4513,8 +4587,10 @@ pub const CodeGen = struct { } const llvm_types_slice = try field_llvm_types.toOwnedSlice(self.allocator); - const name_z = try self.allocator.dupeZ(u8, name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + const struct_ty = existing_llvm_type orelse blk: { + const name_z = try self.allocator.dupeZ(u8, name); + break :blk 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); return .{ @@ -4592,7 +4668,7 @@ pub const CodeGen = struct { } /// Expand #using entries: interleave used struct fields with declared fields. - fn expandStructUsing(self: *CodeGen, sd: ast.StructDecl) !StructInfo { + fn expandStructUsing(self: *CodeGen, sd: ast.StructDecl, existing_llvm_type: ?c.LLVMTypeRef) !StructInfo { var all_names = std.ArrayList([]const u8).empty; var all_types = std.ArrayList(Type).empty; var all_defaults = std.ArrayList(?*Node).empty; @@ -4628,8 +4704,10 @@ pub const CodeGen = struct { for (all_types.items) |ty| { try llvm_types.append(self.allocator, self.typeToLLVM(ty)); } - const name_z = try self.allocator.dupeZ(u8, sd.name); - const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + const struct_ty = existing_llvm_type orelse blk: { + const name_z = try self.allocator.dupeZ(u8, sd.name); + break :blk c.LLVMStructCreateNamed(self.context, name_z.ptr); + }; const llvm_slice = try llvm_types.toOwnedSlice(self.allocator); c.LLVMStructSetBody(struct_ty, if (llvm_slice.len > 0) llvm_slice.ptr else null, @intCast(llvm_slice.len), 0); @@ -4641,32 +4719,43 @@ pub const CodeGen = struct { }; } - fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void { - // Generic struct: store as template instead of registering now + /// Register a struct type name with an opaque LLVM struct (no fields yet). + /// Called during registerTypes phase so other types can reference this name. + fn registerStructName(self: *CodeGen, sd: ast.StructDecl) anyerror!void { if (sd.type_params.len > 0) { - // Register methods for generic structs (as generic templates) - try self.registerStructMethods(sd); try self.generic_struct_templates.put(sd.name, sd); return; } - // Forward declaration: register name early so self-referential *T works + // Create opaque named LLVM struct (body set later in resolveStructFields) + const name_z = try self.allocator.dupeZ(u8, sd.name); + const struct_ty = c.LLVMStructCreateNamed(self.context, name_z.ptr); + try self.type_registry.put(sd.name, .{ .struct_info = .{ .field_names = &.{}, .field_types = &.{}, .field_defaults = &.{}, - .llvm_type = null, + .llvm_type = struct_ty, } }); - // Pre-pass: hoist inline type declarations from field types + // Hoist inline type declarations (anonymous struct/union/enum in field types) for (sd.field_types, 0..) |ft, i| { try self.hoistInlineTypeDecl(sd.name, sd.field_names[i], ft); } + } + + /// Resolve struct field types and set the LLVM struct body. + /// Called during resolveFields phase after all type names are registered. + fn resolveStructFields(self: *CodeGen, sd: ast.StructDecl) anyerror!void { + if (sd.type_params.len > 0) return; + + const existing = self.lookupStructInfo(sd.name) orelse return; + const struct_ty = existing.llvm_type orelse return; const sinfo = if (sd.using_entries.len > 0) - try self.expandStructUsing(sd) + try self.expandStructUsing(sd, struct_ty) else blk: { - const build = try self.buildStructFields(sd.name, sd.field_types); + const build = try self.buildStructFieldsWithType(sd.name, sd.field_types, struct_ty); // Process field defaults: replace #run expressions with comptime global references var resolved_defaults = try self.allocator.alloc(?*Node, sd.field_defaults.len); @@ -4693,9 +4782,23 @@ pub const CodeGen = struct { try self.type_registry.put(sd.name, .{ .struct_info = sinfo }); _ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name }); + } - // Register struct methods after struct is fully registered (methods reference the struct type) - try self.registerStructMethods(sd); + /// Convenience: register name + resolve fields in one call. + /// Used by hoistInlineTypeDecl and resolveEnumLayout for on-demand type creation. + fn registerStructType(self: *CodeGen, sd: ast.StructDecl) anyerror!void { + try self.registerStructName(sd); + try self.resolveStructFields(sd); + } + + fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { + try self.registerUnionName(ud); + try self.resolveUnionFields(ud); + } + + fn registerTaggedEnum(self: *CodeGen, ud: ast.EnumDecl) !void { + try self.registerTaggedEnumName(ud); + try self.resolveTaggedEnumFields(ud); } /// Register methods declared inside a struct body. @@ -5400,8 +5503,9 @@ pub const CodeGen = struct { return result; } - fn registerTaggedEnum(self: *CodeGen, ud: ast.EnumDecl) !void { - // Forward declaration: register name early so self-referential *T works + /// Register a tagged enum name with a placeholder entry. + /// Called during registerTypes phase. + fn registerTaggedEnumName(self: *CodeGen, ud: ast.EnumDecl) !void { try self.type_registry.put(ud.name, .{ .tagged_enum = .{ .variant_names = &.{}, .variant_types = &.{}, @@ -5410,21 +5514,21 @@ pub const CodeGen = struct { .payload_field_index = 0, } }); - // 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); } } + } - // Check if backing type is a struct layout specification + /// Resolve tagged enum variant types and compute LLVM layout. + /// Called during resolveFields phase after all type names are registered. + fn resolveTaggedEnumFields(self: *CodeGen, ud: ast.EnumDecl) !void { const layout_info = try self.resolveEnumLayout(ud); if (layout_info) |layout| { - // Struct-backed layout: use the struct's LLVM type directly try self.enum_backing_types.put(ud.name, layout.tag_llvm_type); - // Resolve variant sx types var variant_sx_types = std.ArrayList(Type).empty; for (ud.variant_types) |vt| { if (vt) |type_node| { @@ -5434,16 +5538,14 @@ pub const CodeGen = struct { } } - const tei_layout = TaggedEnumInfo{ + try self.type_registry.put(ud.name, .{ .tagged_enum = .{ .variant_names = ud.variant_names, .variant_types = try variant_sx_types.toOwnedSlice(self.allocator), .llvm_type = layout.llvm_type, .max_payload_size = layout.payload_size, .payload_field_index = layout.payload_field_index, - }; - try self.type_registry.put(ud.name, .{ .tagged_enum = tei_layout }); + } }); } else { - // Primitive backing type (e.g. enum u32 { ... }) if (ud.backing_type) |bt_node| { const bt = self.resolveType(bt_node); try self.enum_backing_types.put(ud.name, self.typeToLLVM(bt)); @@ -5451,19 +5553,17 @@ pub const CodeGen = struct { const build = try self.buildUnionFields(ud.name, ud.variant_types); - const tei_build = TaggedEnumInfo{ + try self.type_registry.put(ud.name, .{ .tagged_enum = .{ .variant_names = ud.variant_names, .variant_types = build.variant_sx_types, .llvm_type = build.llvm_type, .max_payload_size = build.max_payload_size, .payload_field_index = build.payload_field_index, - }; - try self.type_registry.put(ud.name, .{ .tagged_enum = tei_build }); + } }); } _ = try self.getAnyTypeId(ud.name, .{ .union_type = ud.name }); - // Compute and store variant values (explicit or sequential) const values = try self.allocator.alloc(i64, ud.variant_names.len); for (ud.variant_names, 0..) |_, i| { if (ud.variant_values.len > i and ud.variant_values[i] != null) { @@ -5590,8 +5690,9 @@ pub const CodeGen = struct { }; } - fn registerUnionType(self: *CodeGen, ud: ast.UnionDecl) !void { - // Forward declaration: register name early so self-referential *T works + /// Register a union type name with a placeholder entry. + /// Called during registerTypes phase. + fn registerUnionName(self: *CodeGen, ud: ast.UnionDecl) !void { try self.type_registry.put(ud.name, .{ .union_info = .{ .field_names = &.{}, .field_types = &.{}, @@ -5600,12 +5701,14 @@ pub const CodeGen = struct { .promoted_fields = std.StringHashMap(PromotedField).init(self.allocator), } }); - // Hoist inline type declarations from field types for (ud.field_types, 0..) |ft, i| { try self.hoistInlineTypeDecl(ud.name, ud.field_names[i], ft); } + } - // Compute max field size and resolve field types + /// Resolve union field types and compute LLVM layout. + /// Called during resolveFields phase after all type names are registered. + fn resolveUnionFields(self: *CodeGen, ud: ast.UnionDecl) !void { const data_layout = c.LLVMGetModuleDataLayout(self.module); var field_sx_types = std.ArrayList(Type).empty; var max_size: u64 = 0; @@ -5617,10 +5720,8 @@ pub const CodeGen = struct { if (size > max_size) max_size = size; } - // LLVM type: byte array sized to the largest field const byte_ty = self.i8Type(); const llvm_type = c.LLVMArrayType(byte_ty, @intCast(max_size)); - const resolved_field_types = try field_sx_types.toOwnedSlice(self.allocator); // Build promoted fields map from anonymous struct members @@ -5628,7 +5729,6 @@ pub const CodeGen = struct { for (ud.field_names, 0..) |_, i| { const fty = resolved_field_types[i]; if (fty.isStruct()) { - // Check if this is an anonymous struct (name contains __anon_) const sname = fty.struct_type; if (std.mem.indexOf(u8, sname, ".__anon_") != null) { if (self.lookupStructInfo(sname)) |sinfo| { @@ -5652,8 +5752,6 @@ pub const CodeGen = struct { .promoted_fields = promoted, }; try self.type_registry.put(ud.name, .{ .union_info = uinfo }); - // Note: C-style unions are not registered with the Any type system. - // They can't be meaningfully printed as a whole — access individual fields instead. } fn genTaggedEnumLiteral(self: *CodeGen, el: ast.EnumLiteral, expected_union_name: ?[]const u8) !c.LLVMValueRef {