ffi M5.A.next.3a.B: $args[$i] in type positions — parser + resolver

Step 3 first slice. `$<pack>[<int_literal>]` now parses in
every type position and resolves against the active pack
binding (`pack_arg_types` map set up by `monomorphizePackFn`).

Plumbing:

- src/ast.zig: new `PackIndexTypeExpr { pack_name, index }`
  AST node + `pack_index_type_expr` variant in `Data`.
- src/parser.zig: in `parseTypeExpr`'s `$<ident>` arm, peek
  for `[`. If found, parse a non-negative `int_literal` index
  followed by `]` and emit a `pack_index_type_expr` node.
  Plain `$T` / `$T/Eq` paths unchanged.
- src/ir/lower.zig::resolveTypeWithBindings: handles
  `pack_index_type_expr` first — looks up the pack name in
  `pack_arg_types`, returns `arg_tys[index]` when in range.
  OOB and "no active pack binding" cases emit focused
  diagnostics at the node span.
- src/ir/type_bridge.zig::resolveAstType: handles the same
  node but falls back to `.s64` with a stderr note — the bare
  type_bridge has no access to lowering state. Pack-aware
  callers route through `resolveTypeWithBindings`.
- src/sema.zig: adds `pack_index_type_expr` to the no-op
  arms in `analyzeNode` and `findNodeAtOffset` so the sema
  pass doesn't reject the new variant.

Tests:

- examples/165-pack-type-position.sx (lock-in from 69dcee8)
  flips from parse error to "42 first". Exercises both a
  return-type position (-> $args[0]) AND a local-var
  annotation (second : $args[1] = args[1]); two
  heterogeneous call shapes confirm distinct monos pick
  distinct concrete types per pack index.
- examples/166-pack-type-position-three.sx — three-element
  pack with $args[2] (third element) as return type. Three
  call shapes: (s64,s64,string), (bool,f64,s64),
  (string,string,bool). Prints "third 99 false".

Out of scope (deferred):
- $args[$i] where $i is a comptime-bound expression (only
  literal int supported in this slice).
- $args[$i] in fn-pointer type LITERALS (works for named
  decls but nested fn type expressions need an audit).
- $args[$i] in struct field types.

206/206 example tests + `zig build test` green.
This commit is contained in:
agra
2026-05-27 17:23:47 +03:00
parent 69dcee88cd
commit 3df58febb6
8 changed files with 93 additions and 1 deletions

View File

@@ -9740,6 +9740,33 @@ pub const Lowering = struct {
/// Resolve a type node, checking type_bindings first for generic type params.
fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
// Pack-index in a type position: `$<pack>[<lit>]` resolves to the
// i-th element type of the active pack binding (step 3 of the
// variadic heterogeneous type packs feature). Unblocks parametric
// trampoline bodies (`(*void, $args[0]) -> $args[1]`) in stdlib's
// generic Into(Block) impl. OOB indices emit a diagnostic; no
// binding → bail with a diagnostic-friendly placeholder.
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 type ${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 .s64;
}
}
if (self.diagnostics) |diags| {
diags.addFmt(.err, node.span, "pack-index type ${s}[{}] used outside an active pack binding", .{
pi.pack_name, pi.index,
});
}
return .s64;
}
// `*Self` substitution inside foreign-class member declarations
// — both foreign and sx-defined — resolves to the class's own
// 0-field stub struct (i.e. the opaque Obj-C pointer type).

View File

@@ -26,6 +26,15 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable) TypeId {
.function_type_expr => |ft| resolveFunctionType(&ft, table),
.closure_type_expr => |ct| resolveClosureType(&ct, table),
.tuple_type_expr => |tt| resolveTupleType(&tt, table),
.pack_index_type_expr => {
// Pack-index `$args[N]` in a type position must be resolved
// against an active pack binding — `type_bridge` has no access
// to that state, so this bare path falls back to .s64 with a
// diagnostic logged. The pack-aware caller (lowering's
// `resolveTypeWithBindings`) handles this case directly.
std.debug.print("type_bridge: pack-index type expression encountered outside a pack-aware context\n", .{});
return .s64;
},
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table),
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table),
.inferred_type => .s64, // inferred — default until we have type inference