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:
21
examples/166-pack-type-position-three.sx
Normal file
21
examples/166-pack-type-position-three.sx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
// Variadic heterogeneous type packs — step 3 complex smoke.
|
||||||
|
//
|
||||||
|
// Three-element pack with `$args[2]` (the third element) used in
|
||||||
|
// the return-type position. Confirms:
|
||||||
|
// - Multi-arg packs index past the zeroth element correctly.
|
||||||
|
// - Three distinct call shapes get three distinct monos.
|
||||||
|
// - The return-type slot is correctly substituted per-mono so
|
||||||
|
// the inferred caller type matches what the body actually
|
||||||
|
// returns (string / s64 / bool here).
|
||||||
|
|
||||||
|
#import "modules/std.sx";
|
||||||
|
|
||||||
|
third :: (..$args) -> $args[2] => args[2];
|
||||||
|
|
||||||
|
main :: () -> s32 {
|
||||||
|
a := third(1, 2, "third"); // (s64, s64, string) → "third"
|
||||||
|
b := third(true, 3.14, 99); // (bool, f64, s64) → 99
|
||||||
|
c := third("a", "b", false); // (string, string, bool) → false
|
||||||
|
print("{} {} {}\n", a, b, c);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
10
src/ast.zig
10
src/ast.zig
@@ -56,6 +56,7 @@ pub const Node = struct {
|
|||||||
pointer_type_expr: PointerTypeExpr,
|
pointer_type_expr: PointerTypeExpr,
|
||||||
many_pointer_type_expr: ManyPointerTypeExpr,
|
many_pointer_type_expr: ManyPointerTypeExpr,
|
||||||
optional_type_expr: OptionalTypeExpr,
|
optional_type_expr: OptionalTypeExpr,
|
||||||
|
pack_index_type_expr: PackIndexTypeExpr,
|
||||||
force_unwrap: ForceUnwrap,
|
force_unwrap: ForceUnwrap,
|
||||||
null_coalesce: NullCoalesce,
|
null_coalesce: NullCoalesce,
|
||||||
deref_expr: DerefExpr,
|
deref_expr: DerefExpr,
|
||||||
@@ -364,6 +365,15 @@ pub const TypeExpr = struct {
|
|||||||
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
protocol_constraints: []const []const u8 = &.{}, // e.g. ["Eq", "Hashable"] for $T/Eq/Hashable
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// `$<pack_name>[<index>]` in type position. Resolves to the i-th
|
||||||
|
/// element type of the active pack binding. Step 3 of the variadic
|
||||||
|
/// heterogeneous type packs feature — used in trampoline bodies,
|
||||||
|
/// generic conversions, struct fields parameterised over the pack.
|
||||||
|
pub const PackIndexTypeExpr = struct {
|
||||||
|
pack_name: []const u8,
|
||||||
|
index: u32,
|
||||||
|
};
|
||||||
|
|
||||||
pub const DeferStmt = struct {
|
pub const DeferStmt = struct {
|
||||||
expr: *Node,
|
expr: *Node,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9740,6 +9740,33 @@ pub const Lowering = struct {
|
|||||||
|
|
||||||
/// Resolve a type node, checking type_bindings first for generic type params.
|
/// Resolve a type node, checking type_bindings first for generic type params.
|
||||||
fn resolveTypeWithBindings(self: *Lowering, node: *const Node) TypeId {
|
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
|
// `*Self` substitution inside foreign-class member declarations
|
||||||
// — both foreign and sx-defined — resolves to the class's own
|
// — both foreign and sx-defined — resolves to the class's own
|
||||||
// 0-field stub struct (i.e. the opaque Obj-C pointer type).
|
// 0-field stub struct (i.e. the opaque Obj-C pointer type).
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ pub fn resolveAstType(node: ?*const Node, table: *TypeTable) TypeId {
|
|||||||
.function_type_expr => |ft| resolveFunctionType(&ft, table),
|
.function_type_expr => |ft| resolveFunctionType(&ft, table),
|
||||||
.closure_type_expr => |ct| resolveClosureType(&ct, table),
|
.closure_type_expr => |ct| resolveClosureType(&ct, table),
|
||||||
.tuple_type_expr => |tt| resolveTupleType(&tt, 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),
|
.tuple_literal => |tl| resolveTupleLiteralAsType(&tl, table),
|
||||||
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table),
|
.parameterized_type_expr => |pt| resolveParameterizedType(&pt, table),
|
||||||
.inferred_type => .s64, // inferred — default until we have type inference
|
.inferred_type => .s64, // inferred — default until we have type inference
|
||||||
|
|||||||
@@ -460,7 +460,10 @@ pub const Parser = struct {
|
|||||||
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
|
return try self.createNode(start, .{ .array_type_expr = .{ .length = len_node, .element_type = elem_type } });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic type parameter introduction: $T or $T/Protocol1/Protocol2
|
// Generic type parameter introduction: $T or $T/Protocol1/Protocol2.
|
||||||
|
// Also: pack-index type access $args[<int_literal>] — resolves to
|
||||||
|
// the i-th element type of the active pack binding (step 3 of
|
||||||
|
// the variadic heterogeneous type packs feature).
|
||||||
if (self.current.tag == .dollar) {
|
if (self.current.tag == .dollar) {
|
||||||
self.advance();
|
self.advance();
|
||||||
if (self.current.tag != .identifier) {
|
if (self.current.tag != .identifier) {
|
||||||
@@ -468,6 +471,24 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
const name = self.tokenSlice(self.current);
|
const name = self.tokenSlice(self.current);
|
||||||
self.advance();
|
self.advance();
|
||||||
|
// Pack-index access: $<pack_name>[<int_literal>]
|
||||||
|
if (self.current.tag == .l_bracket) {
|
||||||
|
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 = name,
|
||||||
|
.index = @intCast(idx_val),
|
||||||
|
} });
|
||||||
|
}
|
||||||
// Parse optional protocol constraints: $T/Eq/Hashable
|
// Parse optional protocol constraints: $T/Eq/Hashable
|
||||||
var constraints = std.ArrayList([]const u8).empty;
|
var constraints = std.ArrayList([]const u8).empty;
|
||||||
while (self.current.tag == .slash) {
|
while (self.current.tag == .slash) {
|
||||||
|
|||||||
@@ -884,6 +884,7 @@ pub const Analyzer = struct {
|
|||||||
.pointer_type_expr,
|
.pointer_type_expr,
|
||||||
.many_pointer_type_expr,
|
.many_pointer_type_expr,
|
||||||
.optional_type_expr,
|
.optional_type_expr,
|
||||||
|
.pack_index_type_expr,
|
||||||
.null_literal,
|
.null_literal,
|
||||||
.array_literal,
|
.array_literal,
|
||||||
.parameterized_type_expr,
|
.parameterized_type_expr,
|
||||||
@@ -1326,6 +1327,7 @@ pub fn findNodeAtOffset(node: *Node, offset: u32) ?*Node {
|
|||||||
.pointer_type_expr,
|
.pointer_type_expr,
|
||||||
.many_pointer_type_expr,
|
.many_pointer_type_expr,
|
||||||
.optional_type_expr,
|
.optional_type_expr,
|
||||||
|
.pack_index_type_expr,
|
||||||
.null_literal,
|
.null_literal,
|
||||||
.array_literal,
|
.array_literal,
|
||||||
.parameterized_type_expr,
|
.parameterized_type_expr,
|
||||||
|
|||||||
1
tests/expected/166-pack-type-position-three.exit
Normal file
1
tests/expected/166-pack-type-position-three.exit
Normal file
@@ -0,0 +1 @@
|
|||||||
|
0
|
||||||
1
tests/expected/166-pack-type-position-three.txt
Normal file
1
tests/expected/166-pack-type-position-three.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
third 99 false
|
||||||
Reference in New Issue
Block a user