diff --git a/examples/50-smoke.sx b/examples/50-smoke.sx index dafa090..bd16fca 100644 --- a/examples/50-smoke.sx +++ b/examples/50-smoke.sx @@ -1414,11 +1414,11 @@ END; { gpa_state : GPA = .{ alloc_count = 0 }; gpa := gpa_create(@gpa_state); - p1 := gpa.alloc(gpa.ctx, 64); - p2 := gpa.alloc(gpa.ctx, 128); + p1 := gpa.alloc(64); + p2 := gpa.alloc(128); print("gpa allocs: {}\n", gpa_state.alloc_count); // gpa allocs: 2 - gpa.free(gpa.ctx, p1); - gpa.free(gpa.ctx, p2); + gpa.dealloc(p1); + gpa.dealloc(p2); print("gpa final: {}\n", gpa_state.alloc_count); // gpa final: 0 } @@ -1429,11 +1429,11 @@ END; arena_state : Arena = ---; arena := arena_create(@arena_state, gpa3, 32); // First chunk fits 80 usable bytes - a1 := arena.alloc(arena.ctx, 40); - a2 := arena.alloc(arena.ctx, 40); + a1 := arena.alloc(40); + a2 := arena.alloc(40); print("arena chunks: {}\n", gpa_state3.alloc_count); // arena chunks: 1 // Overflow → new chunk - a3 := arena.alloc(arena.ctx, 16); + a3 := arena.alloc(16); print("arena overflow: {}\n", gpa_state3.alloc_count); // arena overflow: 2 // Verify memory works across chunks p1 : [*]u8 = xx a1; @@ -1456,15 +1456,29 @@ END; stack_buf : [128]u8 = ---; buf_state : BufAlloc = ---; bufalloc := buf_create(@buf_state, @stack_buf[0], 128); - b1 := bufalloc.alloc(bufalloc.ctx, 24); - b2 := bufalloc.alloc(bufalloc.ctx, 24); + b1 := bufalloc.alloc(24); + b2 := bufalloc.alloc(24); print("buf pos: {}\n", buf_state.pos); // buf pos: 48 - b3 := bufalloc.alloc(bufalloc.ctx, 200); + b3 := bufalloc.alloc(200); b3_i : s64 = xx b3; print("buf overflow: {}\n", b3_i); // buf overflow: 0 buf_reset(@buf_state); print("buf reset: {}\n", buf_state.pos); // buf reset: 0 } + { + if 1 == (1,) { + print("1 == (1)\n"); + } + + if (1,) == (1) { + print("(1) == 1\n"); + } + + if (1,) == 1 { + print("1 == 1\n"); + } + } + print("=== DONE ===\n"); } diff --git a/examples/modules/allocators.sx b/examples/modules/allocators.sx index 2d48d29..1cf3b84 100644 --- a/examples/modules/allocators.sx +++ b/examples/modules/allocators.sx @@ -4,10 +4,21 @@ Allocator :: struct { ctx: *void; - alloc: (*void, s64) -> *void; - free: (*void, *void) -> void; + alloc_fn: (*void, s64) -> *void; + free_fn: (*void, *void) -> void; } +allocator_alloc :: (a: Allocator, size: s64) -> *void { + a.alloc_fn(a.ctx, size); +} + +allocator_dealloc :: (a: Allocator, ptr: *void) { + a.free_fn(a.ctx, ptr); +} + +alloc :: ufcs allocator_alloc; +dealloc :: ufcs allocator_dealloc; + // --- GPA: general purpose allocator (malloc/free wrapper) --- GPA :: struct { @@ -27,7 +38,7 @@ gpa_free :: (ctx: *void, ptr: *void) { } gpa_create :: (gpa: *GPA) -> Allocator { - Allocator.{ ctx = gpa, alloc = gpa_alloc, free = gpa_free }; + Allocator.{ ctx = gpa, alloc_fn = gpa_alloc, free_fn = gpa_free }; } // --- Arena: multi-chunk bump allocator (Zig-inspired) --- @@ -47,7 +58,7 @@ arena_add_chunk :: (a: *Arena, min_size: s64) { prev_cap := if a.first != null then a.first.cap else 0; needed := min_size + 16 + 16; len := (prev_cap + needed) * 3 / 2; - raw := a.parent.alloc(a.parent.ctx, len); + raw := a.parent.alloc(len); chunk : *ArenaChunk = xx raw; chunk.next = a.first; chunk.cap = len; @@ -82,7 +93,7 @@ arena_create :: (a: *Arena, parent: Allocator, size: s64) -> Allocator { a.end_index = 0; a.parent = parent; arena_add_chunk(a, size); - Allocator.{ ctx = a, alloc = arena_alloc, free = arena_free }; + Allocator.{ ctx = a, alloc_fn = arena_alloc, free_fn = arena_free }; } arena_reset :: (a: *Arena) { @@ -91,7 +102,7 @@ arena_reset :: (a: *Arena) { it := a.first.next; while it != null { next := it.next; - a.parent.free(a.parent.ctx, it); + a.parent.dealloc(it); it = next; } a.first.next = null; @@ -103,7 +114,7 @@ arena_deinit :: (a: *Arena) { it := a.first; while it != null { next := it.next; - a.parent.free(a.parent.ctx, it); + a.parent.dealloc(it); it = next; } a.first = null; @@ -136,7 +147,7 @@ buf_create :: (b: *BufAlloc, buf: [*]u8, len: s64) -> Allocator { b.buf = buf; b.len = len; b.pos = 0; - Allocator.{ ctx = b, alloc = buf_alloc, free = buf_free }; + Allocator.{ ctx = b, alloc_fn = buf_alloc, free_fn = buf_free }; } buf_reset :: (b: *BufAlloc) { diff --git a/examples/modules/std.sx b/examples/modules/std.sx index 0062e5f..775e79d 100644 --- a/examples/modules/std.sx +++ b/examples/modules/std.sx @@ -31,8 +31,12 @@ context : Context = ---; // --- Slice & string allocation --- +context_alloc :: (size: s64) -> *void { + if context.allocator.ctx != null then context.allocator.alloc(size) else malloc(size); +} + cstring :: (size: s64) -> string { - raw := if context.allocator.ctx != null then context.allocator.alloc(context.allocator.ctx, size + 1) else malloc(size + 1); + raw := context_alloc(size + 1); memset(raw, 0, size + 1); s : string = ---; s.ptr = xx raw; @@ -41,7 +45,7 @@ cstring :: (size: s64) -> string { } alloc_slice :: ($T: Type, count: s64) -> []T { - raw := if context.allocator.ctx != null then context.allocator.alloc(context.allocator.ctx, count * size_of(T)) else malloc(count * size_of(T)); + raw := context_alloc(count * size_of(T)); memset(raw, 0, count * size_of(T)); s : []T = ---; s.ptr = xx raw; diff --git a/src/codegen.zig b/src/codegen.zig index e7cd9d3..6821cb0 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -2207,6 +2207,9 @@ pub const CodeGen = struct { .var_decl => |vd| { try self.registerGlobalVar(vd); }, + .ufcs_alias => |ua| { + try self.ufcs_aliases.put(ua.name, ua.target); + }, else => {}, } } @@ -3560,6 +3563,45 @@ pub const CodeGen = struct { return self.genStringComparison(binop.op, lhs, rhs); } + // Single-element tuple vs scalar: unwrap (T,) → T + { + const unwrap_lhs = lhs_ty.isTuple() and lhs_ty.tuple_type.field_types.len == 1 and !rhs_ty.isTuple(); + const unwrap_rhs = rhs_ty.isTuple() and rhs_ty.tuple_type.field_types.len == 1 and !lhs_ty.isTuple(); + if (unwrap_lhs or unwrap_rhs) { + const eff_lhs_ty = if (unwrap_lhs) lhs_ty.tuple_type.field_types[0] else lhs_ty; + const eff_rhs_ty = if (unwrap_rhs) rhs_ty.tuple_type.field_types[0] else rhs_ty; + const cmp_type = Type.widen(eff_lhs_ty, eff_rhs_ty); + + var lhs_val: c.LLVMValueRef = undefined; + if (unwrap_lhs) { + const alloca = try self.genExpr(binop.lhs); + const elem_llvm = self.typeToLLVM(eff_lhs_ty); + var ftypes = [_]c.LLVMTypeRef{elem_llvm}; + const tup_llvm = c.LLVMStructTypeInContext(self.context, &ftypes, 1, 0); + const gep = self.structGEP(tup_llvm, alloca, 0, "unwrap_l"); + const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm, gep, "elem_l"); + lhs_val = self.convertValue(loaded, eff_lhs_ty, cmp_type); + } else { + lhs_val = try self.genExprAsType(binop.lhs, cmp_type); + } + + var rhs_val: c.LLVMValueRef = undefined; + if (unwrap_rhs) { + const alloca = try self.genExpr(binop.rhs); + const elem_llvm = self.typeToLLVM(eff_rhs_ty); + var ftypes = [_]c.LLVMTypeRef{elem_llvm}; + const tup_llvm = c.LLVMStructTypeInContext(self.context, &ftypes, 1, 0); + const gep = self.structGEP(tup_llvm, alloca, 0, "unwrap_r"); + const loaded = c.LLVMBuildLoad2(self.builder, elem_llvm, gep, "elem_r"); + rhs_val = self.convertValue(loaded, eff_rhs_ty, cmp_type); + } else { + rhs_val = try self.genExprAsType(binop.rhs, cmp_type); + } + + return self.genBinaryOp(binop.op, lhs_val, rhs_val, cmp_type); + } + } + // Tuple comparison: element-wise if (lhs_ty.isTuple() and rhs_ty.isTuple() and (binop.op == .eq or binop.op == .neq or binop.op == .lt or binop.op == .lte or binop.op == .gt or binop.op == .gte)) @@ -6197,9 +6239,22 @@ pub const CodeGen = struct { const method_name = fa.field; const resolved_method = self.ufcs_aliases.get(method_name) orelse method_name; const method_z = self.allocator.dupeZ(u8, resolved_method) catch resolved_method; + // Also check namespace-qualified name for intra-namespace UFCS + const ns_qualified = if (self.current_namespace) |ns| + (std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, resolved_method }) catch null) + else + null; + const ns_qualified_z = if (ns_qualified) |q| (self.allocator.dupeZ(u8, q) catch null) else null; if (self.generic_templates.contains(resolved_method) or - c.LLVMGetNamedFunction(self.module, method_z.ptr) != null) + c.LLVMGetNamedFunction(self.module, method_z.ptr) != null or + (if (ns_qualified) |q| self.generic_templates.contains(q) else false) or + (if (ns_qualified_z) |qz| c.LLVMGetNamedFunction(self.module, qz.ptr) != null else false)) { + // Use namespace-qualified name if that's what exists + const effective_method = if (c.LLVMGetNamedFunction(self.module, method_z.ptr) != null or self.generic_templates.contains(resolved_method)) + resolved_method + else + ns_qualified orelse resolved_method; // Check if receiver is a tuple — if so, splat its elements as leading args const receiver_type = self.inferType(fa.object); const is_tuple = receiver_type == .tuple_type; @@ -6223,7 +6278,7 @@ pub const CodeGen = struct { for (call_node.args, 0..) |arg, i| { ufcs_args[n_tuple + i] = arg; } - return self.genCallByName(resolved_method, .{ + return self.genCallByName(effective_method, .{ .callee = call_node.callee, .args = ufcs_args, }); @@ -6234,7 +6289,7 @@ pub const CodeGen = struct { for (call_node.args, 0..) |arg, i| { ufcs_args[i + 1] = arg; } - return self.genCallByName(resolved_method, .{ + return self.genCallByName(effective_method, .{ .callee = call_node.callee, .args = ufcs_args, }); @@ -6242,7 +6297,9 @@ pub const CodeGen = struct { } // Resolve callee — must be an identifier - if (call_node.callee.data != .identifier) return self.emitError("callee must be an identifier"); + if (call_node.callee.data != .identifier) { + return self.emitError("callee must be an identifier"); + } const callee_name = call_node.callee.data.identifier.name; return self.genCallByName(callee_name, call_node); } @@ -7758,11 +7815,22 @@ pub const CodeGen = struct { } } // UFCS: obj.method(args) → method is the callee name - const method_z = self.allocator.dupeZ(u8, fa.field) catch return null; - if (self.generic_templates.contains(fa.field) or + const resolved = self.ufcs_aliases.get(fa.field) orelse fa.field; + const method_z = self.allocator.dupeZ(u8, resolved) catch return null; + if (self.generic_templates.contains(resolved) or c.LLVMGetNamedFunction(self.module, method_z.ptr) != null) { - return fa.field; + return resolved; + } + // Intra-namespace fallback + if (self.current_namespace) |ns| { + const qualified = std.fmt.allocPrint(self.allocator, "{s}.{s}", .{ ns, resolved }) catch return null; + const qz = self.allocator.dupeZ(u8, qualified) catch return null; + if (self.generic_templates.contains(qualified) or + c.LLVMGetNamedFunction(self.module, qz.ptr) != null) + { + return qualified; + } } } return null; diff --git a/tests/expected/50-smoke.txt b/tests/expected/50-smoke.txt index 67dffe9..ed35685 100644 --- a/tests/expected/50-smoke.txt +++ b/tests/expected/50-smoke.txt @@ -383,4 +383,7 @@ arena deinit: 0 buf pos: 48 buf overflow: 0 buf reset: 0 +1 == (1) +(1) == 1 +1 == 1 === DONE ===