ffi M5.A.next.4.3: $args[$i] in expression position — source construction
Final slice of the .type_tag activation. Sx code can now
construct Type values through the `$<pack>[<int_literal>]`
syntax in expression position. Lowering emits the new
`const_type(TypeId)` opcode; the interp materialises
`Value.type_tag(TypeId)`; reflection intrinsics + cmp_eq
read it kind-honestly.
Plumbing:
- src/parser.zig: `parsePrimary` accepts `$<ident>[<int_literal>]`
at the front of every expression. Emits a `pack_index_type_expr`
AST node — same node already used in TYPE positions in step 3,
now extended to expression positions.
- src/ir/lower.zig: two places teach the new node.
- `lowerExpr` arm: looks up `pack_arg_types[name][index]`, emits
`builder.constType(arg_tys[index])`. OOB / no-binding paths
emit a focused diagnostic + a `constType(.void)` placeholder
(loud failure preserves silent-error budget).
- `resolveTypeArg` arm: the same lookup, but returns the
TypeId directly. Used by the lower-time fast paths in
`tryLowerReflectionCall` + `tryConstBoolCondition` so
`type_name($args[0])`, `type_eq($args[0], s64)`, and
`has_impl(...)` all see the bound TypeId rather than
falling through to the `.s64` default that the silent-arm
rule forbids.
The two arms ensure both runtime AND compile-time paths use
the same source-of-truth (`pack_arg_types`), so per-mono
dispatch via `inline if type_eq($args[0], s64) { ... }` folds
at compile time as expected.
`examples/169-pack-value-dispatch.sx` exercises both shapes:
- `type_name($args[0])` returns the per-mono concrete type
name ("s64", "string", "f64").
- `inline if type_eq($args[0], s64) { ... }` ladder dispatches
per-mono ("got s64", "got string", "got bool", "got other").
209/209 example tests + `zig build test` green.
What's now possible end-to-end:
show :: (..$args) -> string => type_name($args[0]);
show(42) // "s64"
show("hi") // "string"
describe :: (..$args) -> string {
inline if type_eq($args[0], s64) { return "got s64"; }
...
}
The "by the book" activation is complete:
- foundation (const_type opcode, interp variant, helpers) — 4.0
- interp reflection arms (type_name / type_eq / has_impl) — 4.1
- box_any/display audit + bitcast guard — 4.2
- source-language construction via $args[$i] — 4.3
Step 5 (generic Into(Block) impl in stdlib) is now fully
unblocked — its trampoline body can interpolate per-mono types
both in type positions AND in expression positions.
This commit is contained in:
44
examples/169-pack-value-dispatch.sx
Normal file
44
examples/169-pack-value-dispatch.sx
Normal file
@@ -0,0 +1,44 @@
|
||||
// Variadic heterogeneous type packs — step 4 source-construction
|
||||
// path: `$args[$i]` in expression position yields a comptime Type
|
||||
// VALUE (`Value.type_tag(TypeId)` in the interp). Lets builders +
|
||||
// pack-fn bodies dispatch on the i-th pack type at compile time.
|
||||
//
|
||||
// Two usage shapes covered here:
|
||||
//
|
||||
// 1. `type_name($args[0])` — the value-form Type goes through the
|
||||
// reflection intrinsic, picking up the per-mono concrete type.
|
||||
//
|
||||
// 2. `inline if type_eq($args[0], s64) { ... }` — compile-time
|
||||
// branch over the pack element's type. The fold via
|
||||
// `tryConstBoolCondition` reads $args[0] via `resolveTypeArg`,
|
||||
// which the step-4 work taught to walk `pack_arg_types`.
|
||||
//
|
||||
// Lowering: `$args[$i]` in expression position emits a
|
||||
// `const_type(arg_types[i])` IR op. The interp materialises a
|
||||
// `Value.type_tag(TypeId)`. LLVM emit bails (Type is comptime-only).
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
show :: (..$args) -> string => type_name($args[0]);
|
||||
|
||||
describe :: (..$args) -> string {
|
||||
inline if type_eq($args[0], s64) { return "got s64"; }
|
||||
inline if type_eq($args[0], string) { return "got string"; }
|
||||
inline if type_eq($args[0], bool) { return "got bool"; }
|
||||
return "got other";
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
// type_name picks up the per-mono first-arg type.
|
||||
print("{}\n", show(42)); // s64
|
||||
print("{}\n", show("hi")); // string
|
||||
print("{}\n", show(3.14)); // f64
|
||||
|
||||
// inline-if + type_eq picks the right branch per mono.
|
||||
print("{}\n", describe(42)); // got s64
|
||||
print("{}\n", describe("hello")); // got string
|
||||
print("{}\n", describe(true)); // got bool
|
||||
print("{}\n", describe(3.14)); // got other
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -2028,6 +2028,35 @@ pub const Lowering = struct {
|
||||
|
||||
fn lowerExpr(self: *Lowering, node: *const Node) Ref {
|
||||
return switch (node.data) {
|
||||
// Pack-index in expression position: `$<pack>[<lit>]` →
|
||||
// `const_type(arg_types[index])`. Yields a comptime-only
|
||||
// Type value (`Value.type_tag(TypeId)` in the interp).
|
||||
// OOB / no-active-pack-binding → focused diagnostic; the
|
||||
// emitted Ref is a const_type(.void) placeholder so the
|
||||
// verifier downstream catches misuse rather than silently
|
||||
// succeeding with .void.
|
||||
.pack_index_type_expr => |pi| blk: {
|
||||
if (self.pack_arg_types) |pat| {
|
||||
if (pat.get(pi.pack_name)) |arg_tys| {
|
||||
if (pi.index < arg_tys.len) {
|
||||
break :blk self.builder.constType(arg_tys[pi.index]);
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "pack-index value ${s}[{}] out of bounds: '{s}' has {} element{s}", .{
|
||||
pi.pack_name, pi.index, pi.pack_name, arg_tys.len,
|
||||
if (arg_tys.len == 1) @as([]const u8, "") else @as([]const u8, "s"),
|
||||
});
|
||||
}
|
||||
break :blk self.builder.constType(.void);
|
||||
}
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "pack-index value ${s}[{}] used outside an active pack binding", .{
|
||||
pi.pack_name, pi.index,
|
||||
});
|
||||
}
|
||||
break :blk self.builder.constType(.void);
|
||||
},
|
||||
.int_literal => |lit| {
|
||||
// If target is a float type, emit as float literal
|
||||
if (self.target_type) |tt| {
|
||||
@@ -8872,6 +8901,35 @@ pub const Lowering = struct {
|
||||
/// - Direct type names (Vec4 → lookup in TypeTable)
|
||||
/// - type_expr AST nodes
|
||||
fn resolveTypeArg(self: *Lowering, node: *const Node) TypeId {
|
||||
// Pack-index access in a type-arg slot (e.g. `type_name($args[0])`
|
||||
// or `type_eq($args[i], s64)`). Same shape as the
|
||||
// `resolveTypeWithBindings` arm — looks up the bound pack types
|
||||
// and returns the i-th. OOB and no-active-binding emit focused
|
||||
// diagnostics rather than silently defaulting to .s64 (the
|
||||
// catch-all `else` below) — that fall-through is exactly the
|
||||
// "silent unimplemented arm" the project's REJECTED PATTERNS
|
||||
// forbid.
|
||||
if (node.data == .pack_index_type_expr) {
|
||||
const pi = node.data.pack_index_type_expr;
|
||||
if (self.pack_arg_types) |pat| {
|
||||
if (pat.get(pi.pack_name)) |arg_tys| {
|
||||
if (pi.index < arg_tys.len) return arg_tys[pi.index];
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "pack-index ${s}[{}] out of bounds: '{s}' has {} element{s}", .{
|
||||
pi.pack_name, pi.index, pi.pack_name, arg_tys.len,
|
||||
if (arg_tys.len == 1) @as([]const u8, "") else @as([]const u8, "s"),
|
||||
});
|
||||
}
|
||||
return .void;
|
||||
}
|
||||
}
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, node.span, "pack-index ${s}[{}] used outside an active pack binding", .{
|
||||
pi.pack_name, pi.index,
|
||||
});
|
||||
}
|
||||
return .void;
|
||||
}
|
||||
switch (node.data) {
|
||||
.identifier => |id| {
|
||||
// Check type bindings first (from generic monomorphization)
|
||||
|
||||
@@ -2293,6 +2293,40 @@ pub const Parser = struct {
|
||||
|
||||
fn parsePrimary(self: *Parser) anyerror!*Node {
|
||||
const start = self.current.loc.start;
|
||||
// Pack-index in expression position: `$<pack_name>[<int_literal>]`.
|
||||
// Yields a `pack_index_type_expr` AST node (same shape used in
|
||||
// type positions in step 3) — lowering resolves it to a
|
||||
// `const_type(TypeId)` value at the bound pack-arg type. Step 3
|
||||
// already handles the same node in type positions; this arm
|
||||
// extends it to value expressions, completing the round trip
|
||||
// for builders that pass Type values to `type_name`/`type_eq`
|
||||
// /etc at interp time.
|
||||
if (self.current.tag == .dollar) {
|
||||
self.advance();
|
||||
if (self.current.tag != .identifier) {
|
||||
return self.fail("expected pack name after '$'");
|
||||
}
|
||||
const pname = self.tokenSlice(self.current);
|
||||
self.advance();
|
||||
if (self.current.tag != .l_bracket) {
|
||||
return self.fail("expected '[' after '$<pack_name>' (pack indexing requires a literal index in expression position)");
|
||||
}
|
||||
self.advance(); // skip '['
|
||||
if (self.current.tag != .int_literal) {
|
||||
return self.fail("expected integer literal in pack index");
|
||||
}
|
||||
const idx_text = self.tokenSlice(self.current);
|
||||
const idx_val = std.fmt.parseInt(i64, idx_text, 10) catch {
|
||||
return self.fail("invalid integer literal in pack index");
|
||||
};
|
||||
if (idx_val < 0) return self.fail("pack index cannot be negative");
|
||||
self.advance();
|
||||
try self.expect(.r_bracket);
|
||||
return try self.createNode(start, .{ .pack_index_type_expr = .{
|
||||
.pack_name = pname,
|
||||
.index = @intCast(idx_val),
|
||||
} });
|
||||
}
|
||||
switch (self.current.tag) {
|
||||
.int_literal => {
|
||||
const text = self.tokenSlice(self.current);
|
||||
|
||||
1
tests/expected/169-pack-value-dispatch.exit
Normal file
1
tests/expected/169-pack-value-dispatch.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
7
tests/expected/169-pack-value-dispatch.txt
Normal file
7
tests/expected/169-pack-value-dispatch.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
s64
|
||||
string
|
||||
f64
|
||||
got s64
|
||||
got string
|
||||
got bool
|
||||
got other
|
||||
Reference in New Issue
Block a user