diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx new file mode 100644 index 0000000..3695cc9 --- /dev/null +++ b/examples/50-smoke.sx @@ -0,0 +1,618 @@ +#import "modules/std.sx"; + +// ============================================================ +// Comprehensive Smoke Test — exercises every spec feature +// ============================================================ + +// --- Top-level type declarations --- + +Point :: struct { x, y: s32; } + +Color :: enum { red; green; blue; } + +Shape :: enum { + circle: f32; + rect: struct { w, h: f32; }; + none; +} + +Overlay :: union { + f: f32; + i: s32; +} + +Vec2 :: union { + data: [2]f32; + struct { x, y: f32; }; +} + +Defaults :: struct { + a: s32; + b: s32 = 99; + c: s32 = ---; +} + +MyFloat :: f64; + +Perms :: enum flags { read; write; execute; } + +Status :: enum u8 { ok; err; timeout; } + +// --- Top-level functions --- + +add :: (a: s32, b: s32) -> s32 { a + b; } +mul :: (a: s32, b: s32) -> s32 { a * b; } + +identity :: (x: $T) -> T { x; } + +pair_add :: (a: $T, b: $U) -> s64 { + cast(s64) a + cast(s64) b; +} + +typed_sum :: (args: ..s32) -> s32 { + result := 0; + for args { result = result + it; } + result; +} + +apply :: (f: (s32, s32) -> s32, x: s32, y: s32) -> s32 { + f(x, y); +} + +void_return :: () { + return; +} + +implicit_return :: (x: s32) -> s32 { + x * 2; +} + +early_return :: (x: s32) -> s32 { + if x > 10 { return 99; } + x; +} + +// #run compile-time constant +CT_VAL :: #run add(10, 15); + +// #insert helper +gen_code :: () -> string { + return "print(\"insert-ok\\n\");"; +} + +// ============================================================ +main :: { + + // ======================================================== + // 1. LITERALS + // ======================================================== + print("=== 1. Literals ===\n"); + + // Integer literals + print("decimal: {}\n", 42); + print("hex: {}\n", 0xFF); + print("binary: {}\n", 0b1010); + + // Float literal + pi := 3.14; + print("float: {}\n", pi); + + // Explicit f64 + big : f64 = 2.718281828; + print("f64: {}\n", big); + + // Boolean literals + print("true: {}\n", true); + print("false: {}\n", false); + + // String with escapes + print("escapes: hello\tworld\n"); + + // Multi-line string (backtick) + ml := `line1 +line2`; + print("multiline: {}\n", ml); + + // Heredoc string + hd := #string END +raw heredoc +END; + print("heredoc: {}\n", hd); + + // Undefined with type + undef_val : s32 = ---; + undef_val = 77; + print("undef-then-set: {}\n", undef_val); + + // Enum literal (context-inferred) + c : Color = .green; + print("enum-lit: {}\n", c); + + // ======================================================== + // 2. OPERATORS & PRECEDENCE + // ======================================================== + print("=== 2. Operators ===\n"); + + // Arithmetic + print("add: {}\n", 3 + 4); + print("sub: {}\n", 10 - 3); + print("mul: {}\n", 6 * 7); + print("div: {}\n", 20 / 4); + print("mod: {}\n", 17 % 5); + print("neg: {}\n", -(5)); + + // Comparisons + print("eq: {}\n", 5 == 5); + print("neq: {}\n", 5 != 3); + print("lt: {}\n", 3 < 5); + print("gt: {}\n", 5 > 3); + print("le: {}\n", 5 <= 5); + print("ge: {}\n", 5 >= 3); + + // Chained comparisons + v := 50; + print("chain: {}\n", 0 <= v <= 100); + print("chain-gt: {}\n", 100 > v > 0); + print("chain-mixed: {}\n", 100 > v >= 0); + + // Bitwise + print("band: {}\n", 0xFF & 0x0F); + print("bor: {}\n", 1 | 2 | 4); + + // Logical (short-circuit) + print("and: {}\n", true and true); + print("and-false: {}\n", true and false); + print("or: {}\n", false or true); + print("or-false: {}\n", false or false); + + // Compound assignment + ca := 10; + ca += 5; + print("ca+=: {}\n", ca); + ca -= 3; + print("ca-=: {}\n", ca); + ca *= 2; + print("ca*=: {}\n", ca); + ca /= 6; + print("ca/=: {}\n", ca); + + // Precedence + print("prec1: {}\n", 2 + 3 * 4); + print("prec2: {}\n", (2 + 3) * 4); + + // xx explicit cast + big2 : f64 = 200.7; + small : u8 = xx big2; + print("xx-cast: {}\n", small); + + // ======================================================== + // 3. TYPE SYSTEM + // ======================================================== + print("=== 3. Types ===\n"); + + // Primitive types + v_s8 : s8 = 127; + v_s16 : s16 = 32000; + v_s32 : s32 = 100000; + v_u8 : u8 = 255; + v_u16 : u16 = 65000; + v_u32 : u32 = 4000000; + print("s8: {}\n", v_s8); + print("s16: {}\n", v_s16); + print("s32: {}\n", v_s32); + print("u8: {}\n", v_u8); + print("u16: {}\n", v_u16); + print("u32: {}\n", v_u32); + + // Type alias + mf : MyFloat = 1.5; + print("alias: {}\n", mf); + + // --- Structs --- + // Positional literal + p1 : Point = .{ 1, 2 }; + print("struct-pos: {}\n", p1); + + // Type-prefix literal + p2 := Point.{ 3, 4 }; + print("struct-prefix: {}\n", p2); + + // Named fields + p3 := Point.{ y=10, x=20 }; + print("struct-named: {}\n", p3); + + // Shorthand (variable name = field name) + x : s32 = 5; + y : s32 = 6; + p4 := Point.{ x, y }; + print("struct-shorthand: {}\n", p4); + + // Field defaults + d1 : Defaults; + print("defaults: a={} b={}\n", d1.a, d1.b); + + // Field access and assignment + p5 := Point.{ 0, 0 }; + p5.x = 42; + p5.y = 99; + print("field-assign: {}\n", p5); + + // --- Enum (payload-less) --- + ec : Color = .red; + print("enum: {}\n", ec); + + // Backing type + st : Status = .err; + print("backing: {}\n", st); + + // --- Enum (tagged union) --- + sh : Shape = .circle(3.14); + print("tagged: {}\n", sh); + + // Payload access + radius := sh.circle; + print("payload: {}\n", radius); + + // Void variant + sh = .none; + print("void-variant: {}\n", sh); + + // Pattern matching + sh2 : Shape = .rect(.{ 5, 3 }); + if sh2 == { + case .circle: print("match: circle\n"); + case .rect: print("match: rect\n"); + case .none: print("match: none\n"); + } + + // Match as expression + sh3 : Shape = .circle(1.0); + ms := if sh3 == { + case .circle: 10; + case .rect: 20; + case .none: 30; + } + print("match-expr: {}\n", ms); + + // Payload capture (block form) + sh4 : Shape = .circle(9.5); + if sh4 == { + case .circle: (r) { print("capture: {}\n", r); } + case .rect: (sz) { print("capture: {}\n", sz); } + case .none: print("capture: none\n"); + } + + // else arm in match + num := 42; + if num == { + case 1: print("else-match: one\n"); + case 2: print("else-match: two\n"); + else: print("else-match: other\n"); + } + + // Integer pattern matching + code := 2; + if code == { + case 1: print("int-match: one\n"); + case 2: print("int-match: two\n"); + case 3: print("int-match: three\n"); + } + + // Bool conditional + flag := true; + if flag { print("bool: true\n"); } + + // --- Union (untagged) --- + o : Overlay = ---; + o.f = 3.14; + print("union-f: {}\n", o.f); + // Type punning — read same bits as s32 + print("union-i: {}\n", o.i); + + // Union member promotion + uv : Vec2 = ---; + uv.x = 1.0; + uv.y = 2.0; + print("promoted-x: {}\n", uv.x); + print("promoted-data0: {}\n", uv.data[0]); + + // --- Arrays --- + arr : [5]s32 = .[10, 20, 30, 40, 50]; + print("arr[2]: {}\n", arr[2]); + print("arr.len: {}\n", arr.len); + + // --- Slices --- + sl : []s32 = .[1, 2, 3, 4, 5]; + print("sl[0]: {}\n", sl[0]); + print("sl.len: {}\n", sl.len); + + // Subslicing + sub := arr[1..4]; + print("sub: {}\n", sub); + head := arr[..3]; + print("head: {}\n", head); + tail := arr[2..]; + print("tail: {}\n", tail); + + // String subslicing + msg := "hello world"; + print("strsub: {}\n", msg[6..11]); + + // --- Pointers --- + pv := Point.{ 10, 20 }; + ptr := @pv; + print("deref: {}\n", ptr.*); + + // Auto-deref + print("auto-deref: {}\n", ptr.x); + + // Many-pointer + mp : [*]s32 = @arr[0]; + print("mp[0]: {}\n", mp[0]); + print("mp[3]: {}\n", mp[3]); + + // ======================================================== + // 4. CONTROL FLOW + // ======================================================== + print("=== 4. Control Flow ===\n"); + + // If-then-else (inline, as expression) + ite := if true then 1 else 2; + print("ite: {}\n", ite); + + // If block + if 1 < 2 { + print("if-block: yes\n"); + } + + // If block as expression + ibe := 10 + if true { 5; } else { 0; }; + print("if-block-expr: {}\n", ibe); + + // While basic + wi := 0; + while wi < 5 { wi += 1; } + print("while: {}\n", wi); + + // While with break + wb := 0; + while wb < 100 { + if wb == 7 { break; } + wb += 1; + } + print("while-break: {}\n", wb); + + // While with continue + wsum := 0; + wc := 0; + while wc < 10 { + wc += 1; + if wc % 2 == 0 { continue; } + wsum += wc; + } + print("while-continue: {}\n", wsum); + + // For loop basic (using write like example 19) + farr : [4]s32 = .[10, 20, 30, 40]; + write("for:"); + for farr { + write(" "); + write(int_to_string(it)); + } + write("\n"); + + // For with print + write("for-print:"); + for farr { + print(" {}", it); + } + write("\n"); + + // For with it_index + write("for-idx:"); + for farr { + write(" "); + write(int_to_string(it_index)); + } + write("\n"); + + // For with print two args + write("for-2arg:"); + for farr { + print(" {}@{}", it, it_index); + } + write("\n"); + + // For with break + write("for-break:"); + for farr { + if it == 30 { break; } + print(" {}", it); + } + write("\n"); + + // For with continue + write("for-continue:"); + for farr { + if it == 20 { continue; } + print(" {}", it); + } + write("\n"); + + // ======================================================== + // 5. FUNCTIONS & DECLARATIONS + // ======================================================== + print("=== 5. Functions ===\n"); + + // Constant binding + FORTY_TWO :: 42; + print("const: {}\n", FORTY_TWO); + + // Typed constant + TYPED_PI : f64 : 3.14; + print("typed-const: {}\n", TYPED_PI); + + // Variable with default init + di : s32; + print("default-init: {}\n", di); + + // Implicit return + print("implicit-ret: {}\n", implicit_return(21)); + + // Explicit return + print("early-ret: {}\n", early_return(5)); + print("early-ret2: {}\n", early_return(20)); + + // Void return + void_return(); + print("void-return: ok\n"); + + // Generic — single param + print("generic-s32: {}\n", identity(42)); + print("generic-f32: {}\n", identity(1.5)); + + // Generic — multiple params + print("generic-multi: {}\n", pair_add(10, 20)); + + // Lambda + double :: (x: s32) => x * 2; + print("lambda: {}\n", double(7)); + + // Lambda with return type + halve :: (x: f32) -> f32 => x / 2.0; + print("lambda-ret: {}\n", halve(10.0)); + + // Variadic (typed) + print("varargs: {}\n", typed_sum(1, 2, 3, 4, 5)); + + // Spread + spread_arr : [3]s32 = .[10, 20, 30]; + print("spread: {}\n", typed_sum(..spread_arr)); + + // Function pointers + fp : (s32, s32) -> s32 = add; + print("fp: {}\n", fp(3, 4)); + fp = mul; + print("fp-reassign: {}\n", fp(3, 4)); + print("fp-apply: {}\n", apply(add, 10, 20)); + + // ======================================================== + // 6. SCOPING & DEFER + // ======================================================== + print("=== 6. Scoping ===\n"); + + // Scope block with shadowing + sv := 100; + { + sv := 200; + print("inner: {}\n", sv); + } + print("outer: {}\n", sv); + + // Nested scopes (3 levels) + nv := 1; + { + nv := 2; + { + nv := 3; + print("nest3: {}\n", nv); + } + print("nest2: {}\n", nv); + } + print("nest1: {}\n", nv); + + // Multiple defers (LIFO order) + { + defer print("defer-c\n"); + defer print("defer-b\n"); + defer print("defer-a\n"); + } + + // Defer in nested scopes + { + defer print("outer-defer\n"); + { + defer print("inner-defer\n"); + } + } + + // ======================================================== + // 7. BUILT-IN FUNCTIONS + // ======================================================== + print("=== 7. Builtins ===\n"); + + // write + write("write-ok\n"); + + // sqrt + print("sqrt: {}\n", sqrt(9.0)); + + // size_of + print("sizeof-s32: {}\n", size_of(s32)); + print("sizeof-f64: {}\n", size_of(f64)); + + // type_of + category matching + tv := 42; + ttype := type_of(tv); + if ttype == { + case int: print("typeof: int\n"); + case float: print("typeof: float\n"); + else: print("typeof: other\n"); + } + + // type_name + print("typename: {}\n", type_name(Point)); + + // field_count + print("fieldcount: {}\n", field_count(Point)); + + // field_name + print("fieldname0: {}\n", field_name(Point, 0)); + print("fieldname1: {}\n", field_name(Point, 1)); + + // field_value (use any_to_string to avoid sext-on-Any bug) + fv_pt := Point.{ 11, 22 }; + write("fieldval0: "); + write(any_to_string(field_value(fv_pt, 0))); + write("\n"); + write("fieldval1: "); + write(any_to_string(field_value(fv_pt, 1))); + write("\n"); + + // field_index on enum + fi_c : Color = .green; + print("fieldidx: {}\n", field_index(Color, fi_c)); + + // cast + cval : f64 = 3.7; + print("cast: {}\n", cast(s32) cval); + + // ======================================================== + // 8. COMPILE-TIME + // ======================================================== + print("=== 8. Comptime ===\n"); + + // #run constant + print("run-const: {}\n", CT_VAL); + + // #insert with function + #insert gen_code(); + + // ======================================================== + // 9. FLAGS + // ======================================================== + print("=== 9. Flags ===\n"); + + // Combine flags + perm : Perms = .read | .write; + print("flags: {}\n", perm); + + // Test flag + if perm & .read { print("has-read: yes\n"); } + if perm & .execute { print("has-exec: yes\n"); } + + // Cast to int + print("flags-raw: {}\n", cast(s64) perm); + + print("=== DONE ===\n"); +} diff --git a/src/ast.zig b/src/ast.zig index 661f61e..fa2785a 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -89,6 +89,7 @@ pub const FnDecl = struct { return_type: ?*Node, body: *Node, type_params: []const StructTypeParam = &.{}, + is_arrow: bool = false, }; pub const Param = struct { diff --git a/src/codegen.zig b/src/codegen.zig index 93fe37d..eae543d 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -107,17 +107,7 @@ pub const CodeGen = struct { // Symbol table: maps variable names to their alloca pointers named_values: std.StringHashMap(NamedValue), - // Enum type registry: maps enum name to variant list - enum_types: std.StringHashMap([]const []const u8), - // Type alias registry: maps alias name to target type name - type_aliases: std.StringHashMap([]const u8), - // Struct type registry: maps struct name to field info + LLVM type - struct_types: std.StringHashMap(StructInfo), - // Tagged enum registry: maps name to variant info + LLVM type (enums with payloads) - tagged_enum_types: std.StringHashMap(TaggedEnumInfo), - // Union registry: maps name to field info + LLVM type (untagged, C-style) - union_types: std.StringHashMap(UnionInfo), - // Unified type registry: single lookup for all named types + // Unified type registry: single lookup for all named types (structs, enums, unions, aliases) type_registry: std.StringHashMap(TypeRegistryEntry), // Flags enum registry: tracks which enum names are flags flags_enum_types: std.StringHashMap(void), @@ -292,6 +282,34 @@ pub const CodeGen = struct { ty: Type, // sx type }; + /// Unified value lookup result — avoids sequential hash lookups at hot paths. + const ValueLookup = union(enum) { + local: NamedValue, + comptime_global: *ComptimeGlobal, + global_mutable: NamedValue, + + fn ty(self: ValueLookup) Type { + return switch (self) { + .local, .global_mutable => |nv| nv.ty, + .comptime_global => |ct| ct.ty, + }; + } + + fn ptr(self: ValueLookup) c.LLVMValueRef { + return switch (self) { + .local, .global_mutable => |nv| nv.ptr, + .comptime_global => |ct| ct.global, + }; + } + + fn asNamedValue(self: ValueLookup) ?NamedValue { + return switch (self) { + .local, .global_mutable => |nv| nv, + .comptime_global => null, + }; + } + }; + pub fn init(allocator: std.mem.Allocator, module_name: [*:0]const u8, target_config: TargetConfig) CodeGen { const ctx = c.LLVMContextCreate(); const module = c.LLVMModuleCreateWithNameInContext(module_name, ctx); @@ -331,11 +349,6 @@ pub const CodeGen = struct { .builder = builder, .allocator = allocator, .named_values = std.StringHashMap(NamedValue).init(allocator), - .enum_types = std.StringHashMap([]const []const u8).init(allocator), - .type_aliases = std.StringHashMap([]const u8).init(allocator), - .struct_types = std.StringHashMap(StructInfo).init(allocator), - .tagged_enum_types = std.StringHashMap(TaggedEnumInfo).init(allocator), - .union_types = std.StringHashMap(UnionInfo).init(allocator), .type_registry = std.StringHashMap(TypeRegistryEntry).init(allocator), .flags_enum_types = std.StringHashMap(void).init(allocator), .enum_variant_values = std.StringHashMap([]const i64).init(allocator), @@ -367,11 +380,6 @@ pub const CodeGen = struct { pub fn deinit(self: *CodeGen) void { self.named_values.deinit(); - self.enum_types.deinit(); - self.type_aliases.deinit(); - self.struct_types.deinit(); - self.tagged_enum_types.deinit(); - self.union_types.deinit(); self.type_registry.deinit(); self.comptime_globals.deinit(); self.enum_backing_types.deinit(); @@ -393,11 +401,52 @@ pub const CodeGen = struct { } fn getStructInfo(self: *CodeGen, name: []const u8) !StructInfo { - return self.struct_types.get(name) orelse return self.emitErrorFmt("unknown struct type '{s}'", .{name}); + return self.lookupStructInfo(name) orelse + return self.emitErrorFmt("unknown struct type '{s}'", .{name}); } fn getTaggedEnumInfo(self: *CodeGen, name: []const u8) !TaggedEnumInfo { - return self.tagged_enum_types.get(name) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{name}); + return self.lookupTaggedEnumInfo(name) orelse + return self.emitErrorFmt("unknown enum type '{s}'", .{name}); + } + + fn lookupStructInfo(self: *CodeGen, name: []const u8) ?StructInfo { + if (self.type_registry.get(name)) |e| { + if (e == .struct_info) return e.struct_info; + } + return null; + } + + fn lookupTaggedEnumInfo(self: *CodeGen, name: []const u8) ?TaggedEnumInfo { + if (self.type_registry.get(name)) |e| { + if (e == .tagged_enum) return e.tagged_enum; + } + return null; + } + + fn lookupUnionInfo(self: *CodeGen, name: []const u8) ?UnionInfo { + if (self.type_registry.get(name)) |e| { + if (e == .union_info) return e.union_info; + } + return null; + } + + fn lookupEnumVariants(self: *CodeGen, name: []const u8) ?[]const []const u8 { + if (self.type_registry.get(name)) |e| { + if (e == .plain_enum) return e.plain_enum; + } + return null; + } + + fn lookupAlias(self: *CodeGen, name: []const u8) ?[]const u8 { + if (self.type_registry.get(name)) |e| { + if (e == .alias) return e.alias; + } + return null; + } + + fn isRegisteredType(self: *CodeGen, name: []const u8) bool { + return self.type_registry.contains(name); } fn resolveElementType(self: *CodeGen, name: []const u8, comptime kind: []const u8) !Type { @@ -419,6 +468,14 @@ pub const CodeGen = struct { return self.builtins orelse return self.emitError("builtins not available"); } + /// Unified value lookup: checks locals, comptime globals, then global mutables. + fn lookupValue(self: *CodeGen, name: []const u8) ?ValueLookup { + if (self.named_values.get(name)) |nv| return .{ .local = nv }; + if (self.comptime_globals.getPtr(name)) |ct| return .{ .comptime_global = ct }; + if (self.global_mutable_vars.get(name)) |gm| return .{ .global_mutable = gm }; + return null; + } + /// Build an alloca in the entry block of the current function so that /// stack space is reserved once, not on every loop iteration. fn buildEntryBlockAlloca(self: *CodeGen, ty: c.LLVMTypeRef, name: [*:0]const u8) c.LLVMValueRef { @@ -461,8 +518,8 @@ pub const CodeGen = struct { .boolean => self.i1Type(), .string_type, .slice_type => self.getStringStructType(), // slices use same {ptr, i32} layout .enum_type => |name| self.getEnumLLVMType(name), - .struct_type => |name| if (self.struct_types.get(name)) |info| info.llvm_type else unreachable, - .union_type => |name| if (self.tagged_enum_types.get(name)) |info| info.llvm_type else if (self.union_types.get(name)) |info| info.llvm_type else unreachable, + .struct_type => |name| if (self.lookupStructInfo(name)) |info| info.llvm_type else unreachable, + .union_type => |name| if (self.lookupTaggedEnumInfo(name)) |info| info.llvm_type else if (self.lookupUnionInfo(name)) |info| info.llvm_type else unreachable, .array_type => |info| { const elem_ty = Type.fromName(info.element_name) orelse unreachable; return c.LLVMArrayType2(self.typeToLLVM(elem_ty), info.length); @@ -554,7 +611,7 @@ pub const CodeGen = struct { .struct_type => |name| { _ = try self.getAnyTypeId(name, sx_type); // Recursively register struct field types - if (self.struct_types.get(name)) |info| { + if (self.lookupStructInfo(name)) |info| { for (info.field_types) |ft| { try self.preRegisterAnyType(ft); } @@ -610,6 +667,7 @@ pub const CodeGen = struct { // Convert value to i64 const val_as_i64 = switch (ty) { + .void_type => c.LLVMConstInt(i64_ty, 0, 0), .boolean => self.zExt(val, i64_ty, "any_bool"), .signed => |w| if (w <= 32) self.sExt(val, i64_ty, "any_int") @@ -628,7 +686,7 @@ pub const CodeGen = struct { .string_type => self.allocaStoreAsI64(self.getStringStructType(), val, "any_str"), .struct_type => |sname| blk: { // Struct — store to alloca, pass pointer as i64 - const info = self.struct_types.get(sname) orelse + const info = self.lookupStructInfo(sname) orelse return self.getUndef(any_ty); break :blk self.allocaStoreAsI64(info.llvm_type, val, "any_struct"); }, @@ -643,7 +701,7 @@ pub const CodeGen = struct { }, .union_type => |uname| blk: { // Union — store to alloca, pass pointer as i64 - const info = self.tagged_enum_types.get(uname) orelse + const info = self.lookupTaggedEnumInfo(uname) orelse return self.getUndef(any_ty); break :blk self.allocaStoreAsI64(info.llvm_type, val, "any_union"); }, @@ -754,7 +812,7 @@ pub const CodeGen = struct { } fn resolveAlias(self: *CodeGen, name: []const u8) []const u8 { - return self.type_aliases.get(name) orelse name; + return self.lookupAlias(name) orelse name; } fn buildPhiNode(self: *CodeGen, phi_vals: *std.ArrayList(c.LLVMValueRef), phi_bbs: *std.ArrayList(c.LLVMBasicBlockRef), ty: c.LLVMTypeRef, name: [*c]const u8) !c.LLVMValueRef { @@ -982,7 +1040,6 @@ pub const CodeGen = struct { try self.registerTaggedEnum(ed); } else { // Payload-less enum - try self.enum_types.put(ed.name, ed.variant_names); try self.type_registry.put(ed.name, .{ .plain_enum = ed.variant_names }); _ = try self.getAnyTypeId(ed.name, .{ .enum_type = ed.name }); @@ -1025,7 +1082,6 @@ pub const CodeGen = struct { } else if (cd.value.data == .lambda) { try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); } else if (cd.value.data == .type_expr) { - try self.type_aliases.put(cd.name, cd.value.data.type_expr.name); 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 @@ -1038,24 +1094,20 @@ pub const CodeGen = struct { // 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_aliases.put(cd.name, result_ty.struct_type); 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_aliases.put(cd.name, result_ty.struct_type); try self.type_registry.put(cd.name, .{ .alias = result_ty.struct_type }); } else if (result_ty.isUnion()) { - try self.type_aliases.put(cd.name, result_ty.union_type); 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_aliases.put(cd.name, display); try self.type_registry.put(cd.name, .{ .alias = display }); } else { try self.registerTopLevelConstant(cd); @@ -1405,7 +1457,7 @@ pub const CodeGen = struct { 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)) { + if (self.type_registry.contains(mangled_name)) { return .{ .struct_type = mangled_name }; } @@ -1468,7 +1520,6 @@ pub const CodeGen = struct { .type_param_types = try tp_types.toOwnedSlice(self.allocator), .template_name = template_name, }; - try self.struct_types.put(mangled_name, si); try self.type_registry.put(mangled_name, .{ .struct_info = si }); _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); @@ -1497,7 +1548,7 @@ pub const CodeGen = struct { // Try struct first if (self.findStructInBody(fd.body)) |struct_decl| { - if (self.struct_types.contains(mangled_name)) { + if (self.type_registry.contains(mangled_name)) { return .{ .struct_type = mangled_name }; } return self.registerInstantiatedStruct(mangled_name, alias_name, struct_decl); @@ -1505,7 +1556,7 @@ pub const CodeGen = struct { // Try union if (self.findUnionInBody(fd.body)) |union_decl| { - if (self.tagged_enum_types.contains(mangled_name)) { + if (self.type_registry.contains(mangled_name)) { return .{ .union_type = mangled_name }; } return self.registerInstantiatedTaggedEnum(mangled_name, union_decl); @@ -1527,7 +1578,6 @@ pub const CodeGen = struct { .llvm_type = build.llvm_type, .display_name = display_name, }; - try self.struct_types.put(mangled_name, si2); try self.type_registry.put(mangled_name, .{ .struct_info = si2 }); _ = try self.getAnyTypeId(mangled_name, .{ .struct_type = mangled_name }); @@ -1544,7 +1594,6 @@ pub const CodeGen = struct { .max_payload_size = build.max_payload_size, .payload_field_index = build.payload_field_index, }; - try self.tagged_enum_types.put(mangled_name, tei); try self.type_registry.put(mangled_name, .{ .tagged_enum = tei }); _ = try self.getAnyTypeId(mangled_name, .{ .union_type = mangled_name }); @@ -1652,7 +1701,7 @@ pub const CodeGen = struct { if (!sx_ty.isStruct()) return self.typeToLLVM(sx_ty); const sname = self.resolveAlias(sx_ty.struct_type); - const info = self.struct_types.get(sname) orelse return self.typeToLLVM(sx_ty); + const info = self.lookupStructInfo(sname) orelse return self.typeToLLVM(sx_ty); if (self.target_config.isAarch64()) { return self.aarch64ParamABI(info); @@ -1899,11 +1948,9 @@ pub const CodeGen = struct { // 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_aliases.put(qualified_u, 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.enum_types.put(qualified, ed.variant_names); try self.type_registry.put(qualified, .{ .plain_enum = ed.variant_names }); _ = try self.getAnyTypeId(qualified, .{ .enum_type = qualified }); if (ed.backing_type) |bt_node| { @@ -1916,13 +1963,11 @@ pub const CodeGen = struct { 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_aliases.put(qualified_s, 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_aliases.put(qualified_u, ud.name); try self.type_registry.put(qualified_u, .{ .alias = ud.name }); }, .const_decl => |cd| { @@ -1933,7 +1978,6 @@ pub const CodeGen = struct { 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_aliases.put(qualified, cd.value.data.type_expr.name); try self.type_registry.put(qualified, .{ .alias = cd.value.data.type_expr.name }); } }, @@ -2019,8 +2063,11 @@ pub const CodeGen = struct { const name_ptr = c.LLVMGetStructName(llvm_ty); if (name_ptr != null) { const name = std.mem.span(name_ptr); - if (self.struct_types.contains(name)) return .{ .struct_type = name }; - if (self.tagged_enum_types.contains(name)) return .{ .union_type = name }; + if (self.type_registry.get(name)) |e| switch (e) { + .struct_info => return .{ .struct_type = name }, + .tagged_enum => return .{ .union_type = name }, + else => {}, + }; } } // Check for array types @@ -2277,11 +2324,29 @@ pub const CodeGen = struct { const saved_named = self.named_values; self.named_values = std.StringHashMap(NamedValue).init(self.allocator); - // Register with correct types (null return_type = void) - try self.registerFnDecl(fd, fd.name); + // Infer return type from body for => lambdas without explicit annotation + const ret_sx_type = if (fd.return_type != null) self.resolveType(fd.return_type) else if (fd.is_arrow) self.inferType(fd.body) else Type.void_type; - // Generate body inline - const ret_sx_type = self.resolveType(fd.return_type); + // For arrow lambdas with inferred return type, build function manually + if (fd.is_arrow and fd.return_type == null) { + const ret_llvm_type = self.typeToLLVM(ret_sx_type); + var param_llvm_types = std.ArrayList(c.LLVMTypeRef).empty; + for (fd.params) |param| { + try param_llvm_types.append(self.allocator, self.typeToLLVM(self.resolveType(param.type_expr))); + } + const params_slice = try param_llvm_types.toOwnedSlice(self.allocator); + const fn_type = c.LLVMFunctionType( + ret_llvm_type, + if (params_slice.len > 0) params_slice.ptr else null, + @intCast(params_slice.len), + 0, + ); + const name_z2 = try self.allocator.dupeZ(u8, fd.name); + _ = c.LLVMAddFunction(self.module, name_z2.ptr, fn_type); + try self.function_return_types.put(fd.name, ret_sx_type); + } else { + try self.registerFnDecl(fd, fd.name); + } self.current_return_type = ret_sx_type; const name_z = try self.allocator.dupeZ(u8, fd.name); const function = c.LLVMGetNamedFunction(self.module, name_z.ptr) orelse @@ -2460,7 +2525,7 @@ pub const CodeGen = struct { sx_ty = .{ .union_type = uname }; // C-style (untagged) union - if (self.union_types.get(uname)) |info| { + if (self.lookupUnionInfo(uname)) |info| { const alloca = try self.buildNamedAlloca(info.llvm_type, vd.name); if (vd.value == null) { @@ -2652,6 +2717,25 @@ pub const CodeGen = struct { return null; } + // Local lambda: register as function, generate body, done + if (cd.value.data == .lambda) { + const saved_fn = self.current_function; + const saved_bb = self.getCurrentBlock(); + const saved_ret = self.current_return_type; + const saved_named = self.named_values; + self.named_values = std.StringHashMap(NamedValue).init(self.allocator); + + try self.registerLambdaAsFunction(cd.name, cd.value.data.lambda); + try self.genLambdaBody(cd.name, cd.value.data.lambda); + + self.named_values.deinit(); + self.named_values = saved_named; + self.current_return_type = saved_ret; + self.current_function = saved_fn; + self.positionAt(saved_bb); + return null; + } + var sx_ty: Type = Type.s(64); if (cd.type_annotation) |ta| { @@ -2688,6 +2772,8 @@ pub const CodeGen = struct { const enum_name: ?[]const u8 = if (sx_ty.isEnum()) sx_ty.enum_type else null; const init_val = if (cd.value.data == .enum_literal and enum_name != null) self.genEnumLiteral(cd.value.data.enum_literal.name, enum_name.?) + else if (cd.type_annotation != null) + try self.genExprAsType(cd.value, sx_ty) else try self.genExpr(cd.value); @@ -2745,10 +2831,10 @@ pub const CodeGen = struct { // Target must be an identifier if (asgn.target.data != .identifier) return self.emitError("assignment target must be a variable"); const name = asgn.target.data.identifier.name; - const entry = self.named_values.get(name) orelse - self.global_mutable_vars.get(name) orelse { + const lookup = self.lookupValue(name) orelse return self.emitErrorFmt("undefined variable '{s}'", .{name}); - }; + const entry = lookup.asNamedValue() orelse + return self.emitErrorFmt("cannot assign to constant '{s}'", .{name}); // Meta type reassignment: x = Vec4, x = f64, x = test if (entry.ty == .meta_type and asgn.op == .assign) { @@ -2789,7 +2875,7 @@ pub const CodeGen = struct { // Tagged enum reassignment: s = .circle(3.14) or s = .none or s = fn_call() if (entry.ty.isUnion() and asgn.op == .assign) { - if (self.tagged_enum_types.get(entry.ty.union_type)) |info| { + if (self.lookupTaggedEnumInfo(entry.ty.union_type)) |info| { const new_val = try self.genExprAsType(asgn.value, entry.ty); // genExprAsType returns alloca for enum literals, loaded value for calls _ = c.LLVMBuildStore(self.builder, self.loadIfPointer(new_val, info.llvm_type, "union_load"), entry.ptr); @@ -2840,7 +2926,7 @@ pub const CodeGen = struct { // C-style union field assignment if (entry.ty.isUnion()) { const uname = entry.ty.union_type; - if (self.union_types.get(uname)) |info| { + if (self.lookupUnionInfo(uname)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |fidx| { const field_ty = info.field_types[fidx]; const rhs = try self.genExprAsType(asgn.value, field_ty); @@ -2966,22 +3052,15 @@ pub const CodeGen = struct { return self.buildStringSlice(ptr, self.constInt64(@intCast(content.len))); }, .identifier => |ident| { - if (self.named_values.get(ident.name)) |entry| { - const llvm_ty = self.typeToLLVM(entry.ty); - return c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "loadtmp"); - } - // Fall back to comptime globals (lazy resolution) - if (self.comptime_globals.getPtr(ident.name)) |ct| { - if (!ct.is_resolved) { - try self.resolveComptimeGlobal(ct); + if (self.lookupValue(ident.name)) |v| { + switch (v) { + .local => |nv| return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(nv.ty), nv.ptr, "loadtmp"), + .comptime_global => |ct| { + if (!ct.is_resolved) try self.resolveComptimeGlobal(ct); + return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(ct.ty), ct.global, "ct_load"); + }, + .global_mutable => |gm| return c.LLVMBuildLoad2(self.builder, self.typeToLLVM(gm.ty), gm.ptr, "global_load"), } - const llvm_ty = self.typeToLLVM(ct.ty); - return c.LLVMBuildLoad2(self.builder, llvm_ty, ct.global, "ct_load"); - } - // Fall back to global mutable variables (e.g. function pointers from opengl.sx) - if (self.global_mutable_vars.get(ident.name)) |entry| { - const llvm_ty = self.typeToLLVM(entry.ty); - return c.LLVMBuildLoad2(self.builder, llvm_ty, entry.ptr, "global_load"); } // Fall back to function name → function pointer value { @@ -3010,7 +3089,7 @@ pub const CodeGen = struct { if (result_type.isUnion() and (binop.op == .eq or binop.op == .neq)) { const uname = result_type.union_type; const resolved = self.resolveAlias(uname); - const info = self.tagged_enum_types.get(resolved) orelse return self.emitError("unknown tagged enum type"); + const info = self.lookupTaggedEnumInfo(resolved) orelse return self.emitError("unknown tagged enum type"); const tag_ty = self.getEnumLLVMType(resolved); var lhs_val = try self.genExprAsType(binop.lhs, result_type); @@ -3224,7 +3303,7 @@ pub const CodeGen = struct { } // &u.field where u is a C-style union — all fields at offset 0 if (entry.ty.isUnion()) { - if (self.union_types.get(entry.ty.union_type)) |info| { + if (self.lookupUnionInfo(entry.ty.union_type)) |info| { if (self.findNameIndex(info.field_names, fa.field) != null) { return entry.ptr; } @@ -3237,7 +3316,7 @@ pub const CodeGen = struct { // &p.field where p is *Struct — auto-deref through pointer if (entry.ty.isPointer()) { const pointee_name = entry.ty.pointer_type.pointee_name; - if (self.struct_types.get(pointee_name)) |info| { + if (self.lookupStructInfo(pointee_name)) |info| { const loaded_ptr = c.LLVMBuildLoad2(self.builder, self.ptrType(), entry.ptr, "ptr_load"); const idx = try self.findFieldIndex(info.field_names, fa.field, pointee_name); @@ -3338,7 +3417,6 @@ pub const CodeGen = struct { hoisted.name = synthetic_name; try self.registerTaggedEnum(hoisted); } else { - try self.enum_types.put(synthetic_name, inline_ed.variant_names); try self.type_registry.put(synthetic_name, .{ .plain_enum = inline_ed.variant_names }); _ = try self.getAnyTypeId(synthetic_name, .{ .enum_type = synthetic_name }); if (inline_ed.backing_type) |bt_node| { @@ -3387,7 +3465,6 @@ pub const CodeGen = struct { .field_defaults = resolved_defaults, .llvm_type = build.llvm_type, }; - try self.struct_types.put(sd.name, sinfo); try self.type_registry.put(sd.name, .{ .struct_info = sinfo }); _ = try self.getAnyTypeId(sd.name, .{ .struct_type = sd.name }); } @@ -3424,7 +3501,6 @@ pub const CodeGen = struct { .max_payload_size = layout.payload_size, .payload_field_index = layout.payload_field_index, }; - try self.tagged_enum_types.put(ud.name, tei_layout); try self.type_registry.put(ud.name, .{ .tagged_enum = tei_layout }); } else { // Primitive backing type (e.g. enum u32 { ... }) @@ -3442,7 +3518,6 @@ pub const CodeGen = struct { .max_payload_size = build.max_payload_size, .payload_field_index = build.payload_field_index, }; - try self.tagged_enum_types.put(ud.name, tei_build); try self.type_registry.put(ud.name, .{ .tagged_enum = tei_build }); } @@ -3500,7 +3575,7 @@ pub const CodeGen = struct { const resolved = self.resolveAlias(name); if (Type.fromName(resolved) != null) return null; // Must be a registered struct - if (self.struct_types.contains(resolved)) { + if (self.lookupStructInfo(resolved) != null) { return try self.validateEnumLayout(ud.name, resolved); } } @@ -3509,7 +3584,7 @@ pub const CodeGen = struct { } fn validateEnumLayout(self: *CodeGen, enum_name: []const u8, layout_name: []const u8) !EnumLayoutInfo { - const layout = self.struct_types.get(layout_name) orelse { + const layout = self.lookupStructInfo(layout_name) orelse { return self.emitErrorFmt("enum '{s}': layout type '{s}' is not a registered struct", .{ enum_name, layout_name }); }; @@ -3607,7 +3682,7 @@ pub const CodeGen = struct { // 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.struct_types.get(sname)) |sinfo| { + if (self.lookupStructInfo(sname)) |sinfo| { for (sinfo.field_names, 0..) |sf_name, sf_idx| { try promoted.put(sf_name, .{ .struct_name = sname, @@ -3627,7 +3702,6 @@ pub const CodeGen = struct { .total_size = max_size, .promoted_fields = promoted, }; - try self.union_types.put(ud.name, uinfo); 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. @@ -3901,7 +3975,7 @@ pub const CodeGen = struct { const alloca = try self.genStructLiteral(node.data.struct_literal, target_ty.struct_type); // genStructLiteral returns an alloca pointer — load the value for by-value passing const sname = self.resolveAlias(target_ty.struct_type); - if (self.struct_types.get(sname)) |si| { + if (self.lookupStructInfo(sname)) |si| { return c.LLVMBuildLoad2(self.builder, si.llvm_type, alloca, "struct_val"); } return alloca; @@ -3986,13 +4060,13 @@ pub const CodeGen = struct { const pointee_name = target_ty.pointer_type.pointee_name; const src_matches = if (src_ty.isStruct()) std.mem.eql(u8, src_ty.struct_type, pointee_name) or - (if (self.type_aliases.get(src_ty.struct_type)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or - (if (self.type_aliases.get(pointee_name)) |alias| std.mem.eql(u8, alias, src_ty.struct_type) else false) + (if (self.lookupAlias(src_ty.struct_type)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or + (if (self.lookupAlias(pointee_name)) |alias| std.mem.eql(u8, alias, src_ty.struct_type) else false) else if (src_ty.isUnion()) blk: { const uname = src_ty.union_type; break :blk std.mem.eql(u8, uname, pointee_name) or - (if (self.type_aliases.get(uname)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or - (if (self.type_aliases.get(pointee_name)) |alias| std.mem.eql(u8, alias, uname) else false); + (if (self.lookupAlias(uname)) |alias| std.mem.eql(u8, alias, pointee_name) else false) or + (if (self.lookupAlias(pointee_name)) |alias| std.mem.eql(u8, alias, uname) else false); } else if (Type.fromName(pointee_name)) |pointee_ty| src_ty.eql(pointee_ty) else @@ -4012,8 +4086,8 @@ pub const CodeGen = struct { // Struct literals return alloca pointers — load the value for by-value passing if (src_ty.isStruct() and target_ty.isStruct()) { if (c.LLVMGetTypeKind(c.LLVMTypeOf(val)) == c.LLVMPointerTypeKind) { - const info = self.struct_types.get(src_ty.struct_type) orelse - self.struct_types.get(self.resolveAlias(src_ty.struct_type)); + const info = self.lookupStructInfo(src_ty.struct_type) orelse + self.lookupStructInfo(self.resolveAlias(src_ty.struct_type)); if (info) |si| { val = c.LLVMBuildLoad2(self.builder, si.llvm_type, val, "struct_load"); } @@ -4083,7 +4157,7 @@ pub const CodeGen = struct { } if (target_ty.isStruct()) { const sname = target_ty.struct_type; - if (self.struct_types.get(sname)) |info| { + if (self.lookupStructInfo(sname)) |info| { return self.loadFromI64Ptr(i64_val, info.llvm_type, "any_to_struct"); } } @@ -4095,7 +4169,7 @@ pub const CodeGen = struct { } if (target_ty.isUnion()) { const uname = target_ty.union_type; - if (self.tagged_enum_types.get(uname)) |info| { + if (self.lookupTaggedEnumInfo(uname)) |info| { return self.loadFromI64Ptr(i64_val, info.llvm_type, "any_to_union"); } } @@ -4144,7 +4218,7 @@ pub const CodeGen = struct { // Union → int: extract the tag field (index 0) if (src_ty.isUnion() and target_ty.isInt()) { const uname = src_ty.union_type; - if (self.tagged_enum_types.get(uname)) |info| { + if (self.lookupTaggedEnumInfo(uname)) |info| { const tag_llvm_ty = self.getEnumLLVMType(uname); const tag_bits = c.LLVMGetIntTypeWidth(tag_llvm_ty); const tmp = self.buildEntryBlockAlloca(info.llvm_type, "union_cast"); @@ -4333,7 +4407,7 @@ pub const CodeGen = struct { return c.LLVMConstInt(i64_ty, info.field_names.len, 0); } if (ty.isEnum()) { - const variants = self.enum_types.get(ty.enum_type) orelse + const variants = self.lookupEnumVariants(ty.enum_type) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); return c.LLVMConstInt(i64_ty, variants.len, 0); } @@ -4359,7 +4433,7 @@ pub const CodeGen = struct { const info = try self.getStructInfo(ty.struct_type); break :blk .{ info.field_names, ty.struct_type }; } else if (ty.isEnum()) blk: { - const variants = self.enum_types.get(ty.enum_type) orelse + const variants = self.lookupEnumVariants(ty.enum_type) orelse return self.emitErrorFmt("unknown enum type '{s}'", .{ty.enum_type}); break :blk .{ variants, ty.enum_type }; } else if (ty.isUnion()) blk: { @@ -4558,7 +4632,7 @@ pub const CodeGen = struct { } const enum_name = ty.enum_type; const values = self.enum_variant_values.get(enum_name); - const variants = self.enum_types.get(enum_name) orelse return try self.genExpr(call_node.args[1]); + const variants = self.lookupEnumVariants(enum_name) orelse return try self.genExpr(call_node.args[1]); const n = variants.len; const idx = try self.genExpr(call_node.args[1]); @@ -4587,6 +4661,57 @@ pub const CodeGen = struct { if (call_node.args.len != 2) return self.emitError("field_index expects 2 arguments: field_index(T, value)"); const ty = self.resolveType(call_node.args[0]); const i64_type = self.i64Type(); + + // Handle tagged enums (union_type) — extract tag from field 0 + if (ty == .union_type) { + const union_name = ty.union_type; + const info = self.lookupTaggedEnumInfo(union_name) orelse { + _ = try self.genExpr(call_node.args[1]); + return c.LLVMConstInt(i64_type, 0, 0); + }; + const values = self.enum_variant_values.get(union_name); + const n = info.variant_names.len; + + const val = try self.genExpr(call_node.args[1]); + // Extract tag from field 0 of the { tag, payload } struct + const tag_val = self.extractValue(val, 0, "fi_tag"); + const enum_llvm_ty = self.getEnumLLVMType(union_name); + const sw_val = if (c.LLVMTypeOf(tag_val) != enum_llvm_ty) + c.LLVMBuildIntCast2(self.builder, tag_val, enum_llvm_ty, 0, "fi_cast") + else + tag_val; + + const sb = self.buildSwitch(sw_val, @intCast(n), "fi_merge", "fi_default"); + + var phi_vals = std.ArrayList(c.LLVMValueRef).empty; + var phi_bbs = std.ArrayList(c.LLVMBasicBlockRef).empty; + var seen_values = std.ArrayList(u64).empty; + + for (0..n) |i| { + const explicit_val: u64 = if (values) |vals| @bitCast(vals[i]) else i; + var is_dup = false; + for (seen_values.items) |sv| { + if (sv == explicit_val) { + is_dup = true; + break; + } + } + if (is_dup) continue; + try seen_values.append(self.allocator, explicit_val); + const case_bb = self.appendBB("fi_case"); + c.LLVMAddCase(sb.sw, c.LLVMConstInt(enum_llvm_ty, explicit_val, 0), case_bb); + self.positionAt(case_bb); + try self.addPhiCase(&phi_vals, &phi_bbs, c.LLVMConstInt(i64_type, i, 0), sb.merge_bb); + } + + self.positionAt(sb.default_bb); + const neg_one = c.LLVMConstInt(i64_type, @bitCast(@as(i64, -1)), 0); + try self.addPhiCase(&phi_vals, &phi_bbs, neg_one, sb.merge_bb); + + self.positionAt(sb.merge_bb); + return try self.buildPhiNode(&phi_vals, &phi_bbs, i64_type, "fi_result"); + } + if (!ty.isEnum()) { _ = try self.genExpr(call_node.args[1]); return c.LLVMConstInt(i64_type, 0, 0); @@ -4598,7 +4723,7 @@ pub const CodeGen = struct { return c.LLVMConstInt(i64_type, 0, 0); } const values = self.enum_variant_values.get(enum_name); - const variants = self.enum_types.get(enum_name) orelse return try self.genExpr(call_node.args[1]); + const variants = self.lookupEnumVariants(enum_name) orelse return try self.genExpr(call_node.args[1]); const n = variants.len; const val = try self.genExpr(call_node.args[1]); @@ -4736,7 +4861,7 @@ pub const CodeGen = struct { if (entry.ty.isUnion()) { const uname = entry.ty.union_type; // C-style (untagged) union: bitcast pointer and load - if (self.union_types.get(uname)) |info| { + if (self.lookupUnionInfo(uname)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |fidx| { const field_ty = info.field_types[fidx]; return self.loadTyped(field_ty, entry.ptr, "union_field"); @@ -5059,8 +5184,11 @@ pub const CodeGen = struct { const resolved_type: ?Type = blk: { if (fa.object.data == .identifier) { const name = self.resolveAlias(fa.object.data.identifier.name); - if (self.tagged_enum_types.contains(name)) break :blk .{ .union_type = name }; - if (self.struct_types.contains(name)) break :blk .{ .struct_type = name }; + if (self.type_registry.get(name)) |e| switch (e) { + .tagged_enum => break :blk Type{ .union_type = name }, + .struct_info => break :blk Type{ .struct_type = name }, + else => {}, + }; } else { const ty = self.resolveType(fa.object); if (ty.isUnion() or ty.isStruct()) break :blk ty; @@ -5179,11 +5307,12 @@ pub const CodeGen = struct { } // Function pointer indirect call: callee is a variable with function_type if (callee_fn == null) { - const fp_entry = if (self.named_values.get(callee_name)) |e| e - else self.global_mutable_vars.get(callee_name); - if (fp_entry) |entry| { - if (entry.ty.isFunctionType()) { - return self.genIndirectCall(entry, call_node); + if (self.lookupValue(callee_name)) |v| { + const entry = v.asNamedValue(); + if (entry) |e| { + if (e.ty.isFunctionType()) { + return self.genIndirectCall(e, call_node); + } } } return self.emitErrorFmt("undefined function '{s}'", .{callee_name}); @@ -5416,10 +5545,7 @@ pub const CodeGen = struct { // It's a runtime type if it's a named_value, not a type name if (self.named_values.contains(name) and Type.fromName(name) == null and - !self.struct_types.contains(name) and - !self.enum_types.contains(name) and - !self.tagged_enum_types.contains(name) and - !self.type_aliases.contains(name)) + !self.type_registry.contains(name)) { return self.genGenericCallWithRuntimeDispatch(template, call_node, match_tags); } @@ -5492,7 +5618,7 @@ pub const CodeGen = struct { arg_ty.struct_type else ""; - if (self.struct_types.get(struct_name)) |info| { + if (self.lookupStructInfo(struct_name)) |info| { if (info.template_name) |tmpl_name| { if (std.mem.eql(u8, tmpl_name, pte.name)) { // Match generic args against stored type param bindings @@ -6183,6 +6309,7 @@ pub const CodeGen = struct { // Create basic blocks const cond_bb = self.appendBB("for.cond"); const body_bb = self.appendBB("for.body"); + const incr_bb = self.appendBB("for.incr"); const after_bb = self.appendBB("for.after"); self.br(cond_bb); @@ -6216,22 +6343,26 @@ pub const CodeGen = struct { const saved_break_bb = self.loop_break_bb; const saved_continue_bb = self.loop_continue_bb; self.loop_break_bb = after_bb; - self.loop_continue_bb = cond_bb; + self.loop_continue_bb = incr_bb; _ = try self.genExpr(for_expr.body); self.loop_break_bb = saved_break_bb; self.loop_continue_bb = saved_continue_bb; - // Increment it_index + // Fall through to increment block const current_bb = self.getCurrentBlock(); if (c.LLVMGetBasicBlockTerminator(current_bb) == null) { - const inc_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "inc_idx"); - const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i64_type, 1, 0), "next_idx"); - _ = c.LLVMBuildStore(self.builder, next_idx, idx_alloca); - self.br(cond_bb); + self.br(incr_bb); } + // Increment it_index, then branch back to condition + self.positionAt(incr_bb); + const inc_idx = c.LLVMBuildLoad2(self.builder, i64_type, idx_alloca, "inc_idx"); + const next_idx = c.LLVMBuildAdd(self.builder, inc_idx, c.LLVMConstInt(i64_type, 1, 0), "next_idx"); + _ = c.LLVMBuildStore(self.builder, next_idx, idx_alloca); + self.br(cond_bb); + self.positionAt(after_bb); try self.popScope(); @@ -6241,7 +6372,7 @@ pub const CodeGen = struct { fn genEnumLiteral(self: *CodeGen, variant_name: []const u8, enum_type_name: []const u8) c.LLVMValueRef { const enum_ty = self.getEnumLLVMType(enum_type_name); - const variants = self.enum_types.get(enum_type_name) orelse return c.LLVMConstInt(enum_ty, 0, 0); + const variants = self.lookupEnumVariants(enum_type_name) orelse return c.LLVMConstInt(enum_ty, 0, 0); const values = self.enum_variant_values.get(enum_type_name); for (variants, 0..) |v, i| { if (std.mem.eql(u8, v, variant_name)) { @@ -6281,14 +6412,14 @@ pub const CodeGen = struct { const subject_val: c.LLVMValueRef = if (union_name != null) blk: { // Union: load tag from field 0 of the alloca const entry = self.named_values.get(match.subject.data.identifier.name).?; - const info = self.tagged_enum_types.get(union_name.?).?; + const info = self.lookupTaggedEnumInfo(union_name.?).?; break :blk self.loadStructField(info.llvm_type, entry.ptr, 0, self.getEnumLLVMType(union_name.?)); } else try self.genExpr(match.subject); const variants: ?[]const []const u8 = if (union_name) |un| - (if (self.tagged_enum_types.get(un)) |info| info.variant_names else null) + (if (self.lookupTaggedEnumInfo(un)) |info| info.variant_names else null) else if (enum_name) |en| - self.enum_types.get(en) + self.lookupEnumVariants(en) else null; @@ -6372,7 +6503,7 @@ pub const CodeGen = struct { // Payload capture: bind variant payload as a local variable if (arm.capture) |cap_name| { if (union_name) |un| { - const uinfo = self.tagged_enum_types.get(un).?; + const uinfo = self.lookupTaggedEnumInfo(un).?; const pat = arm.pattern.?; if (pat.data == .enum_literal) { const vname = pat.data.enum_literal.name; @@ -6515,14 +6646,13 @@ pub const CodeGen = struct { return tags; } // Named type (struct/enum/union) — get dynamic ID - const sx_type: Type = if (self.struct_types.contains(name)) - .{ .struct_type = name } - else if (self.enum_types.contains(name)) - .{ .enum_type = name } - else if (self.tagged_enum_types.contains(name)) - .{ .union_type = name } - else - .{ .struct_type = name }; // fallback + const sx_type: Type = if (self.type_registry.get(name)) |e| switch (e) { + .struct_info => Type{ .struct_type = name }, + .plain_enum => Type{ .enum_type = name }, + .tagged_enum => Type{ .union_type = name }, + .union_info => Type{ .union_type = name }, + .alias => Type{ .struct_type = name }, + } else .{ .struct_type = name }; // fallback const id = try self.getAnyTypeId(name, sx_type); const tags = try self.allocator.alloc(u64, 1); tags[0] = id; @@ -6714,7 +6844,7 @@ 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 { const resolved = self.resolveAlias(name); - if (self.struct_types.get(resolved)) |info| return info.display_name orelse resolved; + if (self.lookupStructInfo(resolved)) |info| return info.display_name orelse resolved; if (self.resolveTypeFromName(name) != null) return resolved; return null; } @@ -6752,7 +6882,7 @@ pub const CodeGen = struct { } } // Type aliases - if (self.type_aliases.get(name)) |target| return self.resolveTypeFromName(target); + if (self.lookupAlias(name)) |target| return self.resolveTypeFromName(target); return null; } @@ -6776,9 +6906,7 @@ pub const CodeGen = struct { }, .chained_comparison => return .boolean, .identifier => |ident| { - if (self.named_values.get(ident.name)) |entry| return entry.ty; - if (self.comptime_globals.get(ident.name)) |ct| return ct.ty; - if (self.global_mutable_vars.get(ident.name)) |entry| return entry.ty; + if (self.lookupValue(ident.name)) |v| return v.ty(); return Type.s(64); }, .if_expr => |ie| { @@ -6808,7 +6936,7 @@ pub const CodeGen = struct { const obj_ty = blk: { if (fa.object.data == .identifier) { const name = self.resolveAlias(fa.object.data.identifier.name); - if (self.tagged_enum_types.contains(name)) break :blk Type{ .union_type = name }; + if (self.lookupTaggedEnumInfo(name) != null) break :blk Type{ .union_type = name }; } const ty = self.resolveType(fa.object); if (ty.isUnion()) break :blk ty; @@ -6921,11 +7049,9 @@ pub const CodeGen = struct { } // Check if callee is a variable with function pointer type { - const fp_entry = if (self.named_values.get(callee_name)) |e| e - else self.global_mutable_vars.get(callee_name); - if (fp_entry) |entry| { - if (entry.ty.isFunctionType()) { - return entry.ty.function_type.return_type.*; + if (self.lookupValue(callee_name)) |v| { + if (v.ty().isFunctionType()) { + return v.ty().function_type.return_type.*; } } } @@ -6969,14 +7095,14 @@ pub const CodeGen = struct { return obj_ty.vectorElementType() orelse Type.s(64); } if (obj_ty.isStruct()) { - if (self.struct_types.get(obj_ty.struct_type)) |info| { + if (self.lookupStructInfo(obj_ty.struct_type)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |idx| { return info.field_types[idx]; } } } if (obj_ty.isUnion()) { - if (self.union_types.get(obj_ty.union_type)) |info| { + if (self.lookupUnionInfo(obj_ty.union_type)) |info| { if (self.findNameIndex(info.field_names, fa.field)) |idx| { return info.field_types[idx]; } @@ -6984,7 +7110,7 @@ pub const CodeGen = struct { return pf.field_type; } } - if (self.tagged_enum_types.get(obj_ty.union_type)) |info| { + if (self.lookupTaggedEnumInfo(obj_ty.union_type)) |info| { for (info.variant_names, 0..) |vn, i| { if (std.mem.eql(u8, vn, fa.field)) { return info.variant_types[i]; diff --git a/src/parser.zig b/src/parser.zig index 9e34a8e..8af6f07 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -745,6 +745,7 @@ pub const Parser = struct { } // Body: block `{ ... }`, arrow `=> expr;`, #builtin, or #foreign marker + var is_arrow = false; const body = if (self.current.tag == .hash_builtin) blk: { const bi_start = self.current.loc.start; self.advance(); @@ -756,6 +757,7 @@ pub const Parser = struct { try self.expect(.semicolon); break :blk try self.createNode(fi_start, .{ .foreign_expr = {} }); } else if (self.current.tag == .fat_arrow) blk: { + is_arrow = true; self.advance(); const expr = try self.parseExpr(); try self.expect(.semicolon); @@ -774,6 +776,7 @@ pub const Parser = struct { .return_type = return_type, .body = body, .type_params = type_params, + .is_arrow = is_arrow, } }); } diff --git a/tests/expected/03-structs.txt b/tests/expected/03-structs.txt index 13a3e2a..6791631 100644 --- a/tests/expected/03-structs.txt +++ b/tests/expected/03-structs.txt @@ -3,4 +3,4 @@ v2: Vec4{x: 4.000000, y: 1.000000, z: 1.000000, w: 3.000000} v3: Vec4{x: 2.000000, y: 3.000000, z: 4.000000, w: 0.000000} v4: Vec4{x: 9.000000, y: 0.000000, z: 5.000000, w: 6.000000} -Complex{foo: .S(Complex.foo.B{val: hello})} +Complex{foo: .B(Complex.foo.B{val: hello})} diff --git a/tests/expected/10-generic-struct.txt b/tests/expected/10-generic-struct.txt index 99abcd6..ea246f0 100644 --- a/tests/expected/10-generic-struct.txt +++ b/tests/expected/10-generic-struct.txt @@ -8,7 +8,7 @@ v2[1]: 3.000000 scaled: [2.000000, 6.000000, 4.000000] neg: [-1.000000, -3.000000, -2.000000] sqrt(9): 3.000000 -.counter(0.500000) +.user(0.500000) 4 16 8 diff --git a/tests/expected/16-union.txt b/tests/expected/16-union.txt index 3504f10..ba89e01 100644 --- a/tests/expected/16-union.txt +++ b/tests/expected/16-union.txt @@ -1,7 +1,7 @@ circle: .circle(3.140000) radius: 3.140000 -none: .circle -rect: .circle(Shape.rect{w: 4.000000, h: 2.000000}) +none: .none +rect: .rect(Shape.rect{w: 4.000000, h: 2.000000}) sh: .circle(2.710000) rect val: Shape.rect{w: 2.000000, h: 4.000000} matched rect diff --git a/tests/expected/50-smoke.exit b/tests/expected/50-smoke.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/50-smoke.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt new file mode 100644 index 0000000..5230f77 --- /dev/null +++ b/tests/expected/50-smoke.txt @@ -0,0 +1,148 @@ +=== 1. Literals === +decimal: 42 +hex: 255 +binary: 10 +float: 3.140000 +f64: 2.718281 +true: true +false: false +escapes: hello world +multiline: line1 +line2 +heredoc: raw heredoc + +undef-then-set: 77 +enum-lit: .green +=== 2. Operators === +add: 7 +sub: 7 +mul: 42 +div: 5 +mod: 2 +neg: -5 +eq: true +neq: true +lt: true +gt: true +le: true +ge: true +chain: true +chain-gt: true +chain-mixed: true +band: 15 +bor: 7 +and: true +and-false: false +or: true +or-false: false +ca+=: 15 +ca-=: 12 +ca*=: 24 +ca/=: 4 +prec1: 14 +prec2: 20 +xx-cast: 200 +=== 3. Types === +s8: 127 +s16: 32000 +s32: 100000 +u8: 255 +u16: 65000 +u32: 4000000 +alias: 1.500000 +struct-pos: Point{x: 1, y: 2} +struct-prefix: Point{x: 3, y: 4} +struct-named: Point{x: 20, y: 10} +struct-shorthand: Point{x: 5, y: 6} +defaults: a=0 b=99 +field-assign: Point{x: 42, y: 99} +enum: .red +backing: .err +tagged: .circle(3.140000) +payload: 3.140000 +void-variant: .none +match: rect +match-expr: 10 +capture: 9.500000 +else-match: other +bool: true +union-f: 3.140000 +union-i: 1078523331 +promoted-x: 1.000000 +promoted-data0: 1.000000 +arr[2]: 30 +arr.len: 5 +sl[0]: 1 +sl.len: 5 +sub: [20, 30, 40] +head: [10, 20, 30] +tail: [30, 40, 50] +strsub: world +deref: Point{x: 10, y: 20} +auto-deref: 10 +mp[0]: 10 +mp[3]: 40 +=== 4. Control Flow === +ite: 1 +if-block: yes +if-block-expr: 15 +while: 5 +while-break: 7 +while-continue: 25 +for: 10 20 30 40 +for-print: 10 20 30 40 +for-idx: 0 1 2 3 +for-2arg: 10@0 20@1 30@2 40@3 +for-break: 10 20 +for-continue: 10 30 40 +=== 5. Functions === +const: 42 +typed-const: 3.140000 +default-init: 0 +implicit-ret: 42 +early-ret: 5 +early-ret2: 99 +void-return: ok +generic-s32: 42 +generic-f32: 1.500000 +generic-multi: 30 +lambda: 14 +lambda-ret: 5.000000 +varargs: 15 +spread: 60 +fp: 7 +fp-reassign: 12 +fp-apply: 30 +=== 6. Scoping === +inner: 200 +outer: 100 +nest3: 3 +nest2: 2 +nest1: 1 +defer-a +defer-b +defer-c +inner-defer +outer-defer +=== 7. Builtins === +write-ok +sqrt: 3.000000 +sizeof-s32: 4 +sizeof-f64: 8 +typeof: int +typename: Point +fieldcount: 2 +fieldname0: x +fieldname1: y +fieldval0: 11 +fieldval1: 22 +fieldidx: 1 +cast: 3 +=== 8. Comptime === +run-const: 25 +insert-ok +=== 9. Flags === +flags: .read | .write +has-read: yes +flags-raw: 3 +=== DONE ===