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:
agra
2026-05-27 18:52:41 +03:00
parent 55c72af68a
commit fd03b5812f
5 changed files with 144 additions and 0 deletions

View File

@@ -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)