From f1d298764f2cf865603d182ee6277167a41db12b Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 26 Jun 2026 12:47:02 +0300 Subject: [PATCH] feat: `pointee($P: Type) -> Type` comptime reflection builtin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Project a pointer type to its target: `pointee(*X)` -> `X`. The one reflection primitive missing for the `race` result synthesis (`*Task(A)` -> `A` via `field_type(pointee(*Task(A)), 0)`) — reflection could read aggregate fields but was blind to a pointer's target type. Mirrors the `field_type` builtin: declared `#builtin` in std/core.sx, resolved as a lower-time type-call fold in resolveTypeCallWithBindings (src/ir/lower/generic.zig) so it composes in any type-arg slot. `.pointer` -> pointee, `.many_pointer` -> element; a non-pointer arg is a loud diagnostic + `.unresolved` sentinel (no silent fallback). Adversarially reviewed (SHIP). Locked by examples/comptime/0647-comptime-pointee-reflection.sx. Suite green (819/0). PLAN-RACE step 1 of 6. --- .../0647-comptime-pointee-reflection.sx | 26 +++++++++++++++++++ .../0647-comptime-pointee-reflection.exit | 1 + .../0647-comptime-pointee-reflection.stderr | 1 + .../0647-comptime-pointee-reflection.stdout | 4 +++ library/modules/std/core.sx | 4 +++ src/ir/lower/generic.zig | 21 +++++++++++++++ 6 files changed, 57 insertions(+) create mode 100644 examples/comptime/0647-comptime-pointee-reflection.sx create mode 100644 examples/comptime/expected/0647-comptime-pointee-reflection.exit create mode 100644 examples/comptime/expected/0647-comptime-pointee-reflection.stderr create mode 100644 examples/comptime/expected/0647-comptime-pointee-reflection.stdout diff --git a/examples/comptime/0647-comptime-pointee-reflection.sx b/examples/comptime/0647-comptime-pointee-reflection.sx new file mode 100644 index 00000000..ac7b4a8f --- /dev/null +++ b/examples/comptime/0647-comptime-pointee-reflection.sx @@ -0,0 +1,26 @@ +// `pointee($P: Type) -> Type` — comptime reflection for a pointer's target type +// (`pointee(*X)` -> `X`). Folds at lower time like `field_type`, so it composes +// inside other type-arg slots — e.g. project a generic handle `*Box(A)` to its +// payload type `A` via `field_type(pointee(*Box(A)), 0)`. (Foundation for the +// `race` result synthesis, which projects `*Task(A)` -> `A`.) +#import "modules/std.sx"; + +Box :: struct ($R: Type) { value: R; tag: i64; } + +// Project a pointer-to-Box to the Box's payload type: *Box(A) -> A. +Payload :: ($P: Type) -> Type { return field_type(pointee(P), 0); } + +main :: () -> i32 { + // plain pointer + print("pointee(*i64) = {}\n", type_name(pointee(*i64))); + print("pointee(*bool) = {}\n", type_name(pointee(*bool))); + + // pointer to a generic struct → the struct type + print("pointee(*Box(f64)) field0 = {}\n", type_name(field_type(pointee(*Box(f64)), 0))); + + // composed projection, used as a real type + v : Payload(*Box(i64)) = 42; + print("Payload(*Box(i64)) value = {}\n", v); + + return 0; +} diff --git a/examples/comptime/expected/0647-comptime-pointee-reflection.exit b/examples/comptime/expected/0647-comptime-pointee-reflection.exit new file mode 100644 index 00000000..573541ac --- /dev/null +++ b/examples/comptime/expected/0647-comptime-pointee-reflection.exit @@ -0,0 +1 @@ +0 diff --git a/examples/comptime/expected/0647-comptime-pointee-reflection.stderr b/examples/comptime/expected/0647-comptime-pointee-reflection.stderr new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/examples/comptime/expected/0647-comptime-pointee-reflection.stderr @@ -0,0 +1 @@ + diff --git a/examples/comptime/expected/0647-comptime-pointee-reflection.stdout b/examples/comptime/expected/0647-comptime-pointee-reflection.stdout new file mode 100644 index 00000000..0bd15c21 --- /dev/null +++ b/examples/comptime/expected/0647-comptime-pointee-reflection.stdout @@ -0,0 +1,4 @@ +pointee(*i64) = i64 +pointee(*bool) = bool +pointee(*Box(f64)) field0 = f64 +Payload(*Box(i64)) value = 42 diff --git a/library/modules/std/core.sx b/library/modules/std/core.sx index 17eddf2e..2a84f910 100644 --- a/library/modules/std/core.sx +++ b/library/modules/std/core.sx @@ -38,6 +38,10 @@ type_of :: (val: $T) -> Type #builtin; type_name :: ($T: Type) -> string #builtin; field_count :: ($T: Type) -> i64 #builtin; field_name :: ($T: Type, idx: i64) -> string #builtin; +// The target type of a pointer: `pointee(*X)` -> `X`. Comptime reflection, +// folded at lower time like `field_type` (so it composes in any type-arg slot). +// A non-pointer argument is a loud compile error, not a silent fallthrough. +pointee :: ($P: Type) -> Type #builtin; field_value :: (s: $T, idx: i64) -> Any #builtin; is_flags :: ($T: Type) -> bool #builtin; type_is_unsigned :: ($T: Type) -> bool #builtin; diff --git a/src/ir/lower/generic.zig b/src/ir/lower/generic.zig index e470ac96..17c69a90 100644 --- a/src/ir/lower/generic.zig +++ b/src/ir/lower/generic.zig @@ -1424,6 +1424,27 @@ pub fn resolveTypeCallWithBindings(self: *Lowering, cl: *const ast.Call) TypeId }; return self.fieldTypeOf(t, idx, cl.callee.span); } + // pointee($P) -> Type — comptime reflection: the target type of a pointer + // (`pointee(*X)` -> `X`). Folds at lower time like `field_type` so it + // composes inside any type-arg slot. A non-pointer arg is a loud error. + if (std.mem.eql(u8, callee_name, "pointee")) { + if (cl.args.len != 1) { + if (self.diagnostics) |d| + d.addFmt(.err, cl.callee.span, "pointee takes one type: pointee($P)", .{}); + return .unresolved; + } + const t = self.resolveTypeArg(cl.args[0]); + if (t == .unresolved) return .unresolved; + return switch (self.module.types.get(t)) { + .pointer => |p| p.pointee, + .many_pointer => |p| p.element, + else => blk: { + if (self.diagnostics) |d| + d.addFmt(.err, cl.callee.span, "pointee: '{s}' is not a pointer type", .{self.formatTypeName(t)}); + break :blk .unresolved; + }, + }; + } // Built-in: Vector(N, T) if (std.mem.eql(u8, callee_name, "Vector") and cl.args.len == 2) { const length = self.resolveVectorLane(cl.args[0]) orelse return .unresolved;