refactor(types): shrink src/types.zig to editor/parse metadata (A8.2)
Remove the legacy parallel type model's compiler-like surface. The compiler pipeline resolves/lowers/lays out against canonical src/ir/types.zig (TypeId/TypeTable); src/types.zig.Type is now strictly editor-indexing + parse-time name metadata. - src/types.zig: delete the type-resolution surface (widen, bitWidth, isImplicitlyConvertibleTo) and every helper left dead once it was gone (eql, isInt/isFloat/isSigned/isUnsigned, isTuple/isVector, and the already-unused classification predicates isEnum/isUnion/isString/ isStringLike/isAny/optionalChild/sliceElementType/manyPointerElementType/ vectorElementType/isFunctionType/isClosureType/isCallable). Keep the Type union plus the display/name-classification helpers sema/lsp/parser use (fromName, fromTypeExpr, toName, displayName, isStruct/isOptional/isSlice/ isPointer/isManyPointer/isArray, pointerPointeeType). Seal the file with a doc comment. - src/sema.zig: inferExprType no longer calls Type.widen for arithmetic; it approximates the display type as the left operand's (no second resolver in the editor index). - src/ir/type_bridge.zig: delete the dead bridgeType (legacy Type -> TypeId) function + its sole sx_types import; resolveAstType and the AST->TypeId path are untouched. - src/ir/ir.zig: drop the bridgeType re-export. - src/ir/type_bridge.test.zig: drop the two bridgeType tests (function gone). Gate: zig build, zig build test (exit 0), tests/run_examples.sh 361/0, zero examples/expected churn.
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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`).
|
||||
|
||||
10
src/sema.zig
10
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;
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
363
src/types.zig
363
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;
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user