const std = @import("std"); const ast = @import("ast.zig"); const Node = ast.Node; pub const Type = union(enum) { // Variable-width integers (1–64 bits) signed: u8, unsigned: u8, // Fixed-width floats f32, f64, // Other void_type, boolean, string_type, enum_type: []const u8, struct_type: []const u8, union_type: []const u8, array_type: ArrayTypeInfo, slice_type: SliceTypeInfo, pointer_type: PointerTypeInfo, many_pointer_type: ManyPointerTypeInfo, vector_type: VectorTypeInfo, function_type: FunctionTypeInfo, closure_type: ClosureTypeInfo, any_type, usize_type, isize_type, optional_type: OptionalTypeInfo, meta_type: MetaTypeInfo, tuple_type: TupleTypeInfo, pub const SliceTypeInfo = struct { element_name: []const u8, }; pub const PointerTypeInfo = struct { pointee_name: []const u8, }; pub const ManyPointerTypeInfo = struct { element_name: []const u8, }; pub const FunctionTypeInfo = struct { param_types: []const Type, return_type: *const Type, }; pub const ClosureTypeInfo = struct { param_types: []const Type, return_type: *const Type, }; pub const ArrayTypeInfo = struct { element_name: []const u8, length: u32, }; pub const VectorTypeInfo = struct { element_name: []const u8, length: u32, }; pub const OptionalTypeInfo = struct { child_name: []const u8, }; pub const MetaTypeInfo = struct { name: []const u8, }; pub const TupleTypeInfo = struct { field_names: ?[]const []const u8, // null for positional tuples field_types: []const Type, }; /// Content-based equality: compares string fields by content, not pointer identity. pub fn eql(self: Type, other: Type) bool { const Tag = std.meta.Tag(Type); const self_tag: Tag = self; const other_tag: Tag = other; if (self_tag != other_tag) return false; return switch (self) { .signed => |w| w == other.signed, .unsigned => |w| w == other.unsigned, .f32, .f64, .void_type, .boolean, .string_type, .any_type, .usize_type, .isize_type => true, .enum_type => |n| std.mem.eql(u8, n, other.enum_type), .struct_type => |n| std.mem.eql(u8, n, other.struct_type), .union_type => |n| std.mem.eql(u8, n, other.union_type), .array_type => |info| info.length == other.array_type.length and std.mem.eql(u8, info.element_name, other.array_type.element_name), .slice_type => |info| std.mem.eql(u8, info.element_name, other.slice_type.element_name), .pointer_type => |info| std.mem.eql(u8, info.pointee_name, other.pointer_type.pointee_name), .many_pointer_type => |info| std.mem.eql(u8, info.element_name, other.many_pointer_type.element_name), .vector_type => |info| info.length == other.vector_type.length and std.mem.eql(u8, info.element_name, other.vector_type.element_name), .function_type => |info| { const o = other.function_type; if (info.param_types.len != o.param_types.len) return false; for (info.param_types, o.param_types) |a, b| { if (!a.eql(b)) return false; } return info.return_type.eql(o.return_type.*); }, .closure_type => |info| { const o = other.closure_type; if (info.param_types.len != o.param_types.len) return false; for (info.param_types, o.param_types) |a, b| { if (!a.eql(b)) return false; } return info.return_type.eql(o.return_type.*); }, .optional_type => |info| std.mem.eql(u8, info.child_name, other.optional_type.child_name), .meta_type => |info| std.mem.eql(u8, info.name, other.meta_type.name), .tuple_type => |info| { const o = other.tuple_type; if (info.field_types.len != o.field_types.len) return false; for (info.field_types, o.field_types) |a, b| { if (!a.eql(b)) return false; } // If both have names, compare them if (info.field_names != null and o.field_names != null) { for (info.field_names.?, o.field_names.?) |a, b| { if (!std.mem.eql(u8, a, b)) return false; } } return true; }, }; } // Convenience constructors pub fn s(width: u8) Type { return .{ .signed = width }; } pub fn u(width: u8) Type { return .{ .unsigned = width }; } pub fn fromName(name: []const u8) ?Type { if (name.len == 0) return null; return switch (name[0]) { 's' => { if (std.mem.eql(u8, name, "string")) return .string_type; if (name.len >= 2) { const width = std.fmt.parseInt(u8, name[1..], 10) catch return null; if (width >= 1 and width <= 64) return Type.s(width); } return null; }, 'u' => { if (std.mem.eql(u8, name, "usize")) return .usize_type; if (name.len >= 2) { const width = std.fmt.parseInt(u8, name[1..], 10) catch return null; if (width >= 1 and width <= 64) return Type.u(width); } return null; }, 'i' => { if (std.mem.eql(u8, name, "isize")) return .isize_type; return null; }, 'b' => if (std.mem.eql(u8, name, "bool")) .boolean else null, 'f' => { if (std.mem.eql(u8, name, "f32")) return .f32; if (std.mem.eql(u8, name, "f64")) return .f64; return null; }, '?' => if (name.len >= 2) .{ .optional_type = .{ .child_name = name[1..] } } else null, 'A' => if (std.mem.eql(u8, name, "Any")) .any_type else null, 'v' => if (std.mem.eql(u8, name, "void")) .void_type else null, '[' => { // Sentinel-terminated slice: [:0]u8 → string_type if (name.len >= 5 and name[1] == ':') { if (std.mem.indexOfScalar(u8, name, ']')) |close| { const sentinel = name[2..close]; const elem = name[close + 1 ..]; if (std.mem.eql(u8, sentinel, "0") and std.mem.eql(u8, elem, "u8")) { return .string_type; } } } // Many-pointer: [*]T if (name.len >= 4 and name[1] == '*' and name[2] == ']') { return .{ .many_pointer_type = .{ .element_name = name[3..] } }; } return null; }, '*' => if (name.len >= 2) .{ .pointer_type = .{ .pointee_name = name[1..] } } else null, 'V' => { // Vector(N,T) if (name.len >= 10 and std.mem.startsWith(u8, name, "Vector(") and name[name.len - 1] == ')') { const inner = name[7 .. name.len - 1]; if (std.mem.indexOfScalar(u8, inner, ',')) |comma| { const length = std.fmt.parseInt(u32, inner[0..comma], 10) catch return null; const elem_name = inner[comma + 1 ..]; if (elem_name.len > 0) { return .{ .vector_type = .{ .element_name = elem_name, .length = length } }; } } } return null; }, else => null, }; } /// Returns the canonical type name for this type, or null for complex types. /// Used for looking up impl methods on non-struct types (e.g., s32.eq). pub fn toName(self: Type) ?[]const u8 { return switch (self) { .signed => |w| switch (w) { 8 => "s8", 16 => "s16", 32 => "s32", 64 => "s64", else => null, }, .unsigned => |w| switch (w) { 8 => "u8", 16 => "u16", 32 => "u32", 64 => "u64", else => null, }, .f32 => "f32", .f64 => "f64", .boolean => "bool", .string_type => "string", .void_type => "void", .usize_type => "usize", .isize_type => "isize", .struct_type => |n| n, .enum_type => |n| n, .union_type => |n| n, else => null, }; } pub fn fromTypeExpr(node: *Node) ?Type { if (node.data != .type_expr) return null; return fromName(node.data.type_expr.name); } pub fn isEnum(self: Type) bool { return switch (self) { .enum_type => true, else => false, }; } pub fn isStruct(self: Type) bool { return switch (self) { .struct_type => true, else => false, }; } pub fn isUnion(self: Type) bool { return switch (self) { .union_type => true, else => false, }; } pub fn isTuple(self: Type) bool { return switch (self) { .tuple_type => true, else => false, }; } pub fn isOptional(self: Type) bool { return switch (self) { .optional_type => true, else => false, }; } pub fn optionalChild(self: Type) ?[]const u8 { return switch (self) { .optional_type => |info| info.child_name, else => null, }; } pub fn isAny(self: Type) bool { return switch (self) { .any_type => true, else => false, }; } pub fn isString(self: Type) bool { return self == .string_type; } /// Returns true for both `string` (null-terminated) and `[]u8` (byte slice) pub fn isStringLike(self: Type) bool { if (self == .string_type) return true; if (self.isSlice()) { return std.mem.eql(u8, self.slice_type.element_name, "u8"); } return false; } pub fn isSlice(self: Type) bool { return switch (self) { .slice_type => true, else => false, }; } pub fn sliceElementType(self: Type) ?Type { return switch (self) { .slice_type => |info| fromName(info.element_name), else => null, }; } pub fn isPointer(self: Type) bool { return switch (self) { .pointer_type => true, else => false, }; } pub fn pointerPointeeType(self: Type) ?Type { return switch (self) { .pointer_type => |info| fromName(info.pointee_name), else => null, }; } pub fn isManyPointer(self: Type) bool { return switch (self) { .many_pointer_type => true, else => false, }; } pub fn manyPointerElementType(self: Type) ?Type { return switch (self) { .many_pointer_type => |info| fromName(info.element_name), else => null, }; } pub fn isFunctionType(self: Type) bool { return switch (self) { .function_type => true, else => false, }; } pub fn isClosureType(self: Type) bool { return switch (self) { .closure_type => true, else => false, }; } /// Returns true for both bare function pointers and closures pub fn isCallable(self: Type) bool { return switch (self) { .function_type, .closure_type => true, else => false, }; } pub fn isArray(self: Type) bool { return switch (self) { .array_type => true, else => false, }; } pub fn isVector(self: Type) bool { return switch (self) { .vector_type => true, else => false, }; } pub fn vectorElementType(self: Type) ?Type { return switch (self) { .vector_type => |info| fromName(info.element_name), else => null, }; } pub fn isFloat(self: Type) bool { return switch (self) { .f32, .f64 => true, else => false, }; } pub fn isInt(self: Type) bool { return self.isSigned() or self.isUnsigned(); } pub fn isSigned(self: Type) bool { return switch (self) { .signed => true, else => false, }; } pub fn isUnsigned(self: Type) bool { return switch (self) { .unsigned => true, else => false, }; } pub fn bitWidth(self: Type) u32 { return switch (self) { .signed => |w| w, .unsigned => |w| w, .f32 => 32, .f64 => 64, .boolean => 1, .pointer_type, .many_pointer_type, .function_type => 64, .closure_type => 128, // { ptr, ptr } = 16 bytes else => 0, }; } /// Check if this type can be implicitly converted to `target` without `xx`. /// Safe (implicit) conversions: /// - Same type /// - Both unsigned int, target width >= source width /// - Both signed int, target width >= source width /// - Unsigned to signed, target width strictly > source width /// - Any int to any float /// - Float to wider float (f32 → f64) /// Everything else requires `xx`. pub fn isImplicitlyConvertibleTo(self: Type, target: Type) bool { if (self.eql(target)) return true; // string <-> []u8: same layout, bidirectional implicit conversion if (self == .string_type and target.isSlice() and std.mem.eql(u8, target.slice_type.element_name, "u8")) return true; if (self.isSlice() and std.mem.eql(u8, self.slice_type.element_name, "u8") and target == .string_type) return true; // *void is universal pointer (both directions) if (self.isPointer() and target.isPointer()) { if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; if (std.mem.eql(u8, target.pointer_type.pointee_name, "void")) return true; } // *T → [*]T: pointer to element is implicitly convertible to many-pointer // null (*void) → [*]T is also allowed if (self.isPointer() and target.isManyPointer()) { if (std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; return std.mem.eql(u8, self.pointer_type.pointee_name, target.many_pointer_type.element_name); } // [*]T → *void: any many-pointer converts to void pointer if (self.isManyPointer() and target.isPointer()) { return std.mem.eql(u8, target.pointer_type.pointee_name, "void"); } // Tuple → tuple: same field count and each field implicitly convertible if (self.isTuple() and target.isTuple()) { const si = self.tuple_type; const ti = target.tuple_type; if (si.field_types.len != ti.field_types.len) return false; for (si.field_types, ti.field_types) |sf, tf| { if (!sf.isImplicitlyConvertibleTo(tf)) return false; } return true; } // T → ?T: any type implicitly wraps into its optional if (target.isOptional()) { const child_name = target.optional_type.child_name; // null → ?T if (self.isPointer() and std.mem.eql(u8, self.pointer_type.pointee_name, "void")) return true; // ?T → ?U when T → U if (self.isOptional()) { const self_child = fromName(self.optional_type.child_name) orelse return false; const target_child = fromName(child_name) orelse return false; return self_child.isImplicitlyConvertibleTo(target_child); } // T → ?T: check if self matches the child type if (fromName(child_name)) |child_type| { return self.eql(child_type) or self.isImplicitlyConvertibleTo(child_type); } // Non-primitive child (struct/enum name): compare by name return switch (self) { .struct_type => |n| std.mem.eql(u8, n, child_name), .enum_type => |n| std.mem.eql(u8, n, child_name), .union_type => |n| std.mem.eql(u8, n, child_name), else => false, }; } const src_float = self.isFloat(); const dst_float = target.isFloat(); const src_int = self.isInt(); // Float → wider float if (src_float and dst_float) { return target.bitWidth() >= self.bitWidth(); } // Int → float (always safe) if (src_int and dst_float) return true; // Both unsigned → target width >= source width if (self.isUnsigned() and target.isUnsigned()) { return target.bitWidth() >= self.bitWidth(); } // Both signed → target width >= source width if (self.isSigned() and target.isSigned()) { return target.bitWidth() >= self.bitWidth(); } // Unsigned → signed: target must be strictly wider if (self.isUnsigned() and target.isSigned()) { return target.bitWidth() > self.bitWidth(); } // Everything else requires xx return false; } fn fmtAlloc(allocator: std.mem.Allocator, comptime fmt: []const u8, args: anytype) ![]const u8 { var buf: [128]u8 = undefined; const result = std.fmt.bufPrint(&buf, fmt, args) catch return try std.fmt.allocPrint(allocator, fmt, args); return try allocator.dupe(u8, result); } /// Format type name for mangling and display (e.g. "s32", "u8", "f64") pub fn displayName(self: Type, allocator: std.mem.Allocator) ![]const u8 { return switch (self) { .signed => |w| { var buf: [4]u8 = undefined; const result = std.fmt.bufPrint(&buf, "s{d}", .{w}) catch unreachable; return try allocator.dupe(u8, result); }, .unsigned => |w| { var buf: [4]u8 = undefined; const result = std.fmt.bufPrint(&buf, "u{d}", .{w}) catch unreachable; return try allocator.dupe(u8, result); }, .f32 => "f32", .f64 => "f64", .boolean => "bool", .string_type => "string", .void_type => "void", .any_type => "Any", .usize_type => "usize", .isize_type => "isize", .enum_type => |name| name, .struct_type => |name| name, .union_type => |name| name, .slice_type => |info| return fmtAlloc(allocator, "[]{s}", .{info.element_name}), .pointer_type => |info| return fmtAlloc(allocator, "*{s}", .{info.pointee_name}), .many_pointer_type => |info| return fmtAlloc(allocator, "[*]{s}", .{info.element_name}), .array_type => |info| return fmtAlloc(allocator, "[{d}]{s}", .{ info.length, info.element_name }), .vector_type => |info| return fmtAlloc(allocator, "Vector({d},{s})", .{ info.length, info.element_name }), .function_type => |info| { var buf = std.ArrayList(u8).empty; try buf.append(allocator, '('); for (info.param_types, 0..) |pt, i| { if (i > 0) try buf.appendSlice(allocator, ", "); try buf.appendSlice(allocator, try pt.displayName(allocator)); } try buf.append(allocator, ')'); if (!std.meta.eql(info.return_type.*, Type.void_type)) { try buf.appendSlice(allocator, " -> "); try buf.appendSlice(allocator, try info.return_type.displayName(allocator)); } return try buf.toOwnedSlice(allocator); }, .closure_type => |info| { var buf = std.ArrayList(u8).empty; try buf.appendSlice(allocator, "Closure("); for (info.param_types, 0..) |pt, i| { if (i > 0) try buf.appendSlice(allocator, ", "); try buf.appendSlice(allocator, try pt.displayName(allocator)); } try buf.append(allocator, ')'); if (!std.meta.eql(info.return_type.*, Type.void_type)) { try buf.appendSlice(allocator, " -> "); try buf.appendSlice(allocator, try info.return_type.displayName(allocator)); } return try buf.toOwnedSlice(allocator); }, .optional_type => |info| return fmtAlloc(allocator, "?{s}", .{info.child_name}), .meta_type => |info| info.name, .tuple_type => |info| { var buf = std.ArrayList(u8).empty; try buf.append(allocator, '('); for (info.field_types, 0..) |ft, i| { if (i > 0) try buf.appendSlice(allocator, ", "); if (info.field_names) |names| { try buf.appendSlice(allocator, names[i]); try buf.appendSlice(allocator, ": "); } try buf.appendSlice(allocator, try ft.displayName(allocator)); } try buf.append(allocator, ')'); return try buf.toOwnedSlice(allocator); }, }; } /// Widen two types to a common type for binary operations. /// Used for arithmetic type promotion (e.g., s16 + s32 → s32, int + float → float). pub fn widen(a: Type, b: Type) Type { // Same type → return it if (a.eql(b)) return a; // Tuple + tuple → return a if same field count if (a.isTuple() and b.isTuple()) { if (a.tuple_type.field_types.len == b.tuple_type.field_types.len) return a; } // Vector + vector of same dimensions → return a if (a.isVector() and b.isVector()) return a; // Vector + scalar → return vector (scalar will be broadcast) if (a.isVector() and !b.isVector()) return a; if (b.isVector() and !a.isVector()) return b; const a_float = a.isFloat(); const b_float = b.isFloat(); const a_int = a.isInt(); const b_int = b.isInt(); // Both float → wider float if (a_float and b_float) { return if (a.bitWidth() >= b.bitWidth()) a else b; } // int + float → float if (a_int and b_float) return b; if (b_int and a_float) return a; // Both signed → wider signed if (a.isSigned() and b.isSigned()) { return Type.s(@intCast(@max(a.bitWidth(), b.bitWidth()))); } // Both unsigned → wider unsigned if (a.isUnsigned() and b.isUnsigned()) { return Type.u(@intCast(@max(a.bitWidth(), b.bitWidth()))); } // signed + unsigned (mixed) if (a_int and b_int) { const aw = a.bitWidth(); const bw = b.bitWidth(); const max_w = @max(aw, bw); // If same width, need one extra bit for sign; otherwise max is enough const need: u32 = if (aw == bw) max_w + 1 else max_w; const capped: u8 = @intCast(@min(need, 128)); return Type.s(capped); } // Optional types: widen inner types if (a.isOptional() and b.isOptional()) return a; // Pointer types: both are pointers → return first (all are opaque ptr at LLVM level) if ((a.isPointer() or a.isManyPointer()) and (b.isPointer() or b.isManyPointer())) return a; return a; } };