diff --git a/examples/168-pack-reflection-intrinsics.sx b/examples/168-pack-reflection-intrinsics.sx new file mode 100644 index 0000000..184c23e --- /dev/null +++ b/examples/168-pack-reflection-intrinsics.sx @@ -0,0 +1,62 @@ +// Variadic heterogeneous type packs — step 3: type-reflection +// intrinsics. +// +// Three comptime helpers used by pack-fn bodies to branch on +// type identity / protocol membership: +// +// type_name(T) -> string // display name of T +// type_eq(T1, T2) -> bool // structural TypeId equality +// has_impl(P, T) -> bool // T has a reachable impl for P +// +// All three fold to compile-time constants and are accepted by +// `tryConstBoolCondition`, so `inline if type_eq(...)` / +// `inline if has_impl(...)` collapse to a single branch at lower +// time — no runtime cost. +// +// `has_impl`'s protocol arg accepts both shapes: +// - plain protocol name: `has_impl(Allocator, CAllocator)`. +// - parameterised call: `has_impl(Wrap(s64), s32)` — the args +// match the impl's protocol type-args exactly. + +#import "modules/std.sx"; +#import "modules/allocators.sx"; + +// User-defined parameterised protocol + an impl, so has_impl can +// confirm parameterised matching works with a known-true case. +Wrap :: protocol(Target: Type) { + wrap :: () -> Target; +} + +impl Wrap(s64) for s32 { + wrap :: (self: s32) -> s64 => xx self; +} + +main :: () -> s32 { + // type_name — display names. + print("{} {} {}\n", type_name(s64), type_name(string), type_name(bool)); + + // type_eq — structural equality on TypeIds. + print("{} {} {} {}\n", + type_eq(s64, s64), + type_eq(s64, string), + type_eq(*s64, *s64), + type_eq(*s64, *s32)); + + // inline-if folds type_eq at lower time. + inline if type_eq(s64, s64) { + print("inline-if folded: same\n"); + } else { + print("inline-if folded: different\n"); + } + + // has_impl — plain protocol (Allocator is unary). + print("Allocator/CAllocator: {}\n", has_impl(Allocator, CAllocator)); + print("Allocator/s64: {}\n", has_impl(Allocator, s64)); + + // has_impl — parameterised protocol (Wrap takes a Target type arg). + print("Wrap(s64)/s32: {}\n", has_impl(Wrap(s64), s32)); + print("Wrap(s64)/bool: {}\n", has_impl(Wrap(s64), bool)); + print("Wrap(bool)/s32: {}\n", has_impl(Wrap(bool), s32)); + + return 0; +} diff --git a/src/ir/lower.zig b/src/ir/lower.zig index 4c54221..373f47e 100644 --- a/src/ir/lower.zig +++ b/src/ir/lower.zig @@ -2803,6 +2803,15 @@ pub const Lowering = struct { } return false; } + if (std.mem.eql(u8, cname, "type_eq") and c.args.len >= 2) { + const a = self.resolveTypeArg(c.args[0]); + const b = self.resolveTypeArg(c.args[1]); + return a == b; + } + if (std.mem.eql(u8, cname, "has_impl") and c.args.len >= 2) { + const ty = self.resolveTypeArg(c.args[1]); + return self.computeHasImpl(c.args[0], ty); + } } }, else => {}, @@ -2810,6 +2819,50 @@ pub const Lowering = struct { return null; } + /// Shared implementation for the `has_impl(P, T)` builtin and its + /// `tryConstBoolCondition` arm. The protocol expression is either: + /// - Plain `Hash` (identifier / type_expr) → walks + /// `protocol_thunk_map["Hash\x00"]`. + /// - Parameterised `Into(Block)` (call) → walks `param_impl_map` + /// keyed by `"

\x00\x00"`. + /// Returns false on any malformed protocol-arg shape (caller + /// reports a diagnostic if it wants). + fn computeHasImpl(self: *Lowering, proto_node: *const Node, ty: TypeId) bool { + switch (proto_node.data) { + .identifier => |id| return self.hasImplPlain(id.name, ty), + .type_expr => |te| return self.hasImplPlain(te.name, ty), + .call => |c| { + const p_name: []const u8 = switch (c.callee.data) { + .identifier => |id| id.name, + .type_expr => |te| te.name, + else => return false, + }; + // Resolve protocol type args. Each goes through + // `resolveTypeArg` so type aliases / generics / pack- + // indexed types all work as protocol args. + var arg_mangles = std.ArrayList(u8).empty; + defer arg_mangles.deinit(self.alloc); + for (c.args, 0..) |a, i| { + if (i > 0) arg_mangles.append(self.alloc, 0) catch return false; + const aty = self.resolveTypeArg(a); + arg_mangles.appendSlice(self.alloc, self.mangleTypeName(aty)) catch return false; + } + const ty_mangled = self.mangleTypeName(ty); + const key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}\x00{s}", .{ + p_name, arg_mangles.items, ty_mangled, + }) catch return false; + return self.param_impl_map.contains(key); + }, + else => return false, + } + } + + fn hasImplPlain(self: *Lowering, p_name: []const u8, ty: TypeId) bool { + const ty_name = self.formatTypeName(ty); + const thunk_key = std.fmt.allocPrint(self.alloc, "{s}\x00{s}", .{ p_name, ty_name }) catch return false; + return self.protocol_thunk_map.contains(thunk_key); + } + /// Evaluate a compile-time condition for `inline if`. /// Handles: `ident == .variant`, `ident != .variant`, `ident == int`, `ident != int`. fn evalComptimeCondition(self: *Lowering, node: *const Node) ?bool { @@ -8697,6 +8750,31 @@ pub const Lowering = struct { const sid = self.module.types.internString(tn_str); return self.builder.constString(sid); } + if (std.mem.eql(u8, name, "type_eq")) { + // type_eq(T1, T2) → const_bool — comptime TypeId equality. + // TypeIds are interned per structural shape so equality on + // them matches the user's intuition: `type_eq(s64, s64)` is + // true, `type_eq(*s64, *s64)` is true, distinct shapes are + // false. Pack-indexed types (`$args[0]`) resolve through + // `resolveTypeArg` → `resolveTypeWithBindings`. + if (c.args.len < 2) return self.builder.constBool(false); + const a = self.resolveTypeArg(c.args[0]); + const b = self.resolveTypeArg(c.args[1]); + return self.builder.constBool(a == b); + } + if (std.mem.eql(u8, name, "has_impl")) { + // has_impl(P, T) → const_bool. Returns true when type T has + // a reachable impl for protocol P. P is either: + // - plain protocol name (`Hash`, `Eq`) for unary protocols; + // - parameterised call like `Into(Block)` — for protocols + // with type args, the args must be fully spelled. + // Delegates to `computeHasImpl` (shared with the + // `tryConstBoolCondition` arm so `inline if has_impl(...)` + // folds at compile time). + if (c.args.len < 2) return self.builder.constBool(false); + const ty = self.resolveTypeArg(c.args[1]); + return self.builder.constBool(self.computeHasImpl(c.args[0], ty)); + } if (std.mem.eql(u8, name, "is_flags")) { const ty = self.resolveTypeArg(c.args[0]); if (!ty.isBuiltin()) { diff --git a/tests/expected/168-pack-reflection-intrinsics.exit b/tests/expected/168-pack-reflection-intrinsics.exit new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/expected/168-pack-reflection-intrinsics.exit @@ -0,0 +1 @@ +0 diff --git a/tests/expected/168-pack-reflection-intrinsics.txt b/tests/expected/168-pack-reflection-intrinsics.txt new file mode 100644 index 0000000..1f552fa --- /dev/null +++ b/tests/expected/168-pack-reflection-intrinsics.txt @@ -0,0 +1,8 @@ +s64 string bool +true false true false +inline-if folded: same +Allocator/CAllocator: true +Allocator/s64: false +Wrap(s64)/s32: true +Wrap(s64)/bool: false +Wrap(bool)/s32: false