Files
sx/examples/168-pack-reflection-intrinsics.sx
agra 8b457ffc44 ffi M5.A.next.3b: type_eq + has_impl comptime intrinsics
Step 3 second slice. Adds two reflection builtins used by
pack-fn bodies to branch on type identity / protocol
membership at compile time. type_name already existed
(lower.zig:8693); reused as-is.

  type_eq(T1, T2)   -> bool   structural TypeId equality
  has_impl(P, T)    -> bool   T has a reachable impl for P

Both are wired through `tryConstBoolCondition` so the inline-if
ladder folds them at lower time — `inline if type_eq(...)` /
`inline if has_impl(...)` collapse to a single branch with no
runtime instructions, perfect for guard-based dispatch inside
pack-fn bodies.

`has_impl`'s protocol arg accepts two shapes:
- plain protocol name: `has_impl(Allocator, CAllocator)` →
  walks `protocol_thunk_map["Allocator\x00CAllocator"]`.
- parameterised call: `has_impl(Into(Block), s64)` →
  builds the param_impl_map key `"Into\x00Block\x00s64"`
  and checks containment. The protocol type-args resolve
  through `resolveTypeArg` so type aliases, generics, and
  pack-indexed types all work as protocol args.

`computeHasImpl` is the shared implementation between the
runtime builtin path and the `tryConstBoolCondition` fast
path so both branches stay in sync.

`examples/168-pack-reflection-intrinsics.sx` exercises every
shape:
- type_name for primitive types.
- type_eq with both equal + unequal cases, including pointer
  types (s64 vs *s64).
- inline-if folding type_eq.
- has_impl with a real plain-protocol impl
  (Allocator/CAllocator → true; Allocator/s64 → false).
- has_impl with a user-defined parameterised protocol
  (Wrap(s64)/s32 → true; mismatched target args → false).

208/208 example tests + `zig build test` green.

Caveat: plain-protocol has_impl uses `protocol_thunk_map`
which is lazily populated when an `xx` cast or protocol
dispatch creates the thunks. For a static check before any
dispatch, that could false-negative. Allocator/CAllocator
works in 168 because stdlib's startup uses CAllocator through
the Allocator protocol — the thunks already exist by the time
has_impl runs. A more robust static check (walk fn_ast_map for
"<T_name>.<method>" entries against the protocol's method
list) is deferred to a follow-up if needed.

LSP "undefined variable" warnings on type names in expression
position (s64, *s64, Wrap(s64), etc. passed to type_eq /
has_impl) are cosmetic — sema doesn't know these intrinsics
accept types as args. Tracked separately.
2026-05-27 17:48:39 +03:00

63 lines
2.1 KiB
Plaintext

// 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;
}