diff --git a/src/ir/ir.zig b/src/ir/ir.zig index 144f77d..ceb0000 100644 --- a/src/ir/ir.zig +++ b/src/ir/ir.zig @@ -64,7 +64,6 @@ pub const LLVMEmitter = emit_llvm.LLVMEmitter; pub const type_bridge = @import("type_bridge.zig"); pub const resolveAstType = type_bridge.resolveAstType; -pub const bridgeType = type_bridge.bridgeType; pub const jni_descriptor = @import("jni_descriptor.zig"); pub const jni_java_emit = @import("jni_java_emit.zig"); diff --git a/src/ir/type_bridge.test.zig b/src/ir/type_bridge.test.zig index 4b0312e..571eea4 100644 --- a/src/ir/type_bridge.test.zig +++ b/src/ir/type_bridge.test.zig @@ -9,42 +9,6 @@ const TypeId = types.TypeId; const TypeInfo = types.TypeInfo; const TypeTable = types.TypeTable; -test "bridgeType: primitives" { - const alloc = std.testing.allocator; - var table = TypeTable.init(alloc); - defer table.deinit(); - - try std.testing.expectEqual(TypeId.s32, type_bridge.bridgeType(.{ .signed = 32 }, &table, null)); - try std.testing.expectEqual(TypeId.u8, type_bridge.bridgeType(.{ .unsigned = 8 }, &table, null)); - try std.testing.expectEqual(TypeId.f64, type_bridge.bridgeType(.f64, &table, null)); - try std.testing.expectEqual(TypeId.void, type_bridge.bridgeType(.void_type, &table, null)); - try std.testing.expectEqual(TypeId.bool, type_bridge.bridgeType(.boolean, &table, null)); - try std.testing.expectEqual(TypeId.string, type_bridge.bridgeType(.string_type, &table, null)); - try std.testing.expectEqual(TypeId.any, type_bridge.bridgeType(.any_type, &table, null)); -} - -test "bridgeType: composite types" { - const alloc = std.testing.allocator; - var table = TypeTable.init(alloc); - defer table.deinit(); - - // Pointer - const ptr_id = type_bridge.bridgeType(.{ .pointer_type = .{ .pointee_name = "s32" } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .pointer = .{ .pointee = .s32 } }, table.get(ptr_id)); - - // Slice - const slice_id = type_bridge.bridgeType(.{ .slice_type = .{ .element_name = "u8" } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .slice = .{ .element = .u8 } }, table.get(slice_id)); - - // Array - const arr_id = type_bridge.bridgeType(.{ .array_type = .{ .element_name = "f32", .length = 4 } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .array = .{ .element = .f32, .length = 4 } }, table.get(arr_id)); - - // Optional - const opt_id = type_bridge.bridgeType(.{ .optional_type = .{ .child_name = "s64" } }, &table, null); - try std.testing.expectEqual(TypeInfo{ .optional = .{ .child = .s64 } }, table.get(opt_id)); -} - test "resolveAstType: primitive type_expr" { const alloc = std.testing.allocator; var table = TypeTable.init(alloc); diff --git a/src/ir/type_bridge.zig b/src/ir/type_bridge.zig index c5662c5..6145395 100644 --- a/src/ir/type_bridge.zig +++ b/src/ir/type_bridge.zig @@ -2,7 +2,6 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const ast = @import("../ast.zig"); const Node = ast.Node; -const sx_types = @import("../types.zig"); const ir_types = @import("types.zig"); const TypeId = ir_types.TypeId; const TypeInfo = ir_types.TypeInfo; @@ -106,122 +105,8 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable, alias_map: AliasMap }; } -// ── types.Type → TypeId ───────────────────────────────────────────────── -// Translate an existing codegen Type value into an IR TypeId. Used when -// we have access to the codegen's resolved type info (Phase 3+). - -pub fn bridgeType(ty: sx_types.Type, table: *TypeTable, alias_map: AliasMap) TypeId { - return switch (ty) { - .signed => |w| switch (w) { - 8 => .s8, - 16 => .s16, - 32 => .s32, - 64 => .s64, - // Non-standard width: intern the exact width rather than quantising - // to s64 (which would silently change the type's size). - else => table.intern(.{ .signed = w }), - }, - .unsigned => |w| switch (w) { - 8 => .u8, - 16 => .u16, - 32 => .u32, - 64 => .u64, - else => table.intern(.{ .unsigned = w }), - }, - .f32 => .f32, - .f64 => .f64, - .void_type => .void, - .boolean => .bool, - .string_type => .string, - .any_type => .any, - .usize_type => .usize, - .isize_type => .isize, - .enum_type => |name| resolveNamedType(name, .@"enum", table), - .struct_type => |name| resolveNamedType(name, .@"struct", table), - .union_type => |name| resolveNamedType(name, .@"union", table), - .array_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.arrayOf(elem, info.length); - }, - .slice_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.sliceOf(elem); - }, - .pointer_type => |info| blk: { - const pointee = resolveTypeName(info.pointee_name, table, alias_map); - break :blk table.ptrTo(pointee); - }, - .many_pointer_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.manyPtrTo(elem); - }, - .optional_type => |info| blk: { - const child = resolveTypeName(info.child_name, table, alias_map); - break :blk table.optionalOf(child); - }, - .vector_type => |info| blk: { - const elem = resolveTypeName(info.element_name, table, alias_map); - break :blk table.vectorOf(elem, info.length); - }, - .function_type => |info| blk: { - const alloc = table.alloc; - var param_ids = std.ArrayList(TypeId).empty; - for (info.param_types) |pt| { - param_ids.append(alloc, bridgeType(pt, table, alias_map)) catch unreachable; - } - const ret_id = bridgeType(info.return_type.*, table, alias_map); - break :blk table.functionType(param_ids.items, ret_id); - }, - .closure_type => |info| blk: { - const alloc = table.alloc; - var param_ids = std.ArrayList(TypeId).empty; - for (info.param_types) |pt| { - param_ids.append(alloc, bridgeType(pt, table, alias_map)) catch unreachable; - } - const ret_id = bridgeType(info.return_type.*, table, alias_map); - break :blk table.closureType(param_ids.items, ret_id); - }, - .tuple_type => |info| blk: { - const alloc = table.alloc; - var field_ids = std.ArrayList(TypeId).empty; - for (info.field_types) |ft| { - field_ids.append(alloc, bridgeType(ft, table, alias_map)) catch unreachable; - } - var name_ids: ?[]const StringId = null; - if (info.field_names) |names| { - var ids = std.ArrayList(StringId).empty; - for (names) |n| { - ids.append(alloc, table.internString(n)) catch unreachable; - } - name_ids = ids.items; - } - break :blk table.intern(.{ .tuple = .{ - .fields = field_ids.items, - .names = name_ids, - } }); - }, - .meta_type => .any, // meta types map to Any for now - .unresolved => .unresolved, - }; -} - // ── Internal helpers ───────────────────────────────────────────────────── -const NamedKind = enum { @"struct", @"enum", @"union" }; - -fn resolveNamedType(name: []const u8, kind: NamedKind, table: *TypeTable) TypeId { - // Check if primitive first - if (resolveTypePrimitive(name)) |id| return id; - - // Register as a named type - const name_id = table.internString(name); - return switch (kind) { - .@"struct" => table.intern(.{ .@"struct" = .{ .name = name_id, .fields = &.{} } }), - .@"enum" => table.intern(.{ .@"enum" = .{ .name = name_id, .variants = &.{} } }), - .@"union" => table.intern(.{ .@"union" = .{ .name = name_id, .fields = &.{} } }), - }; -} - /// Resolve a bare type name. The algorithm lives in `type_resolver.zig` /// (`TypeResolver.resolveNamed`, the single source); `type_bridge` forwards the /// caller-threaded `alias_map` (the single-source `ProgramIndex.type_alias_map`). diff --git a/src/sema.zig b/src/sema.zig index 01391e2..ef24a7a 100644 --- a/src/sema.zig +++ b/src/sema.zig @@ -565,7 +565,7 @@ pub const Analyzer = struct { /// Infer an approximate editor `Type` for an expression (hover/completion; /// metadata only — NOT a compiler type decision, which uses `TypeId`). /// Uses fn_signatures for call return types, struct_types for field access, - /// symbols for identifier types, and Type.widen for arithmetic promotion. + /// and symbols for identifier types. pub fn inferExprType(self: *Analyzer, node: *const Node) Type { return switch (node.data) { .int_literal => Type.s(64), @@ -578,9 +578,13 @@ pub const Analyzer = struct { switch (binop.op) { .eq, .neq, .lt, .lte, .gt, .gte, .and_op, .or_op, .in_op => return .boolean, else => { + // Editor display only: approximate an arithmetic result as + // its left operand's type (or the right when the left is + // unresolved). Numeric promotion is a compiler decision on + // `TypeId`, never recomputed here. const lhs_ty = self.inferExprType(binop.lhs); - const rhs_ty = self.inferExprType(binop.rhs); - return Type.widen(lhs_ty, rhs_ty); + if (lhs_ty == .unresolved) return self.inferExprType(binop.rhs); + return lhs_ty; }, } }, diff --git a/src/types.zig b/src/types.zig index 2a98a91..3c2d1f7 100644 --- a/src/types.zig +++ b/src/types.zig @@ -2,12 +2,13 @@ const std = @import("std"); const ast = @import("ast.zig"); const Node = ast.Node; -/// Editor metadata type model, used only by `src/sema.zig` (the language-server -/// symbol/type index) for navigation, completion, and hover. NOT the compiler's -/// source of truth: lowering, codegen, and layout use the canonical -/// `TypeId` / `TypeTable` model in `src/ir/types.zig`. Do not expand this to -/// carry new compiler semantics; the architecture endpoint (phase A8) is to -/// delete it or reduce it to display-only data derived from `TypeId`. +/// Editor-indexing and parse-time name metadata — used by `src/sema.zig` (the +/// language-server symbol/type index) for navigation, completion, and hover, and +/// by `src/parser.zig` for parse-time primitive-name classification. This is NOT +/// a compiler type model: it carries no type-resolution surface (no widening, +/// convertibility, or layout). The canonical model the compiler resolves, lowers, +/// and lays out against is `TypeId` / `TypeTable` in `src/ir/types.zig`. Keep this +/// display- and classification-only; never add resolution semantics here. pub const Type = union(enum) { // Variable-width integers (1–64 bits) signed: u8, @@ -86,61 +87,6 @@ pub const Type = union(enum) { 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, .unresolved => 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 }; @@ -255,13 +201,6 @@ pub const Type = union(enum) { 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, @@ -269,20 +208,6 @@ pub const Type = union(enum) { }; } - 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, @@ -290,33 +215,6 @@ pub const Type = union(enum) { }; } - 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, @@ -324,13 +222,6 @@ pub const Type = union(enum) { }; } - 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, @@ -352,35 +243,6 @@ pub const Type = union(enum) { }; } - 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, @@ -388,156 +250,6 @@ pub const Type = union(enum) { }; } - 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 @@ -621,65 +333,4 @@ pub const Type = union(enum) { }, }; } - - /// 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; - } };