forward declaration

This commit is contained in:
agra
2026-02-24 15:10:02 +02:00
parent b98711a1d3
commit 97475d6cfe
9 changed files with 494 additions and 174 deletions

View File

@@ -1,7 +1,7 @@
std :: #import "modules/std.sx";
//flat
#import "modules/math.sx";
#import "modules/math";
main :: () -> s32 {
{

View File

@@ -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];

View File

@@ -1,5 +1,5 @@
#import "modules/std.sx";
#import "modules/math.sx";
#import "modules/math";
test :: () -> s32 {
0;

41
examples/issue-01.sx Normal file
View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 {