lang 2.3: TYPE-position pack projection xs.T (tuple type + closure sig)
`xs.T` projects each pack element's protocol type-arg into a type list, usable in TYPE/signature positions: - tuple type `(..xs.T)` → e.g. `(s64, string)` (new resolveTupleTypeWithBindings) - closure sig `Closure(..xs.T) -> R` → e.g. `Closure(s64, s64) -> s64`, which contextually types a closure literal (resolveClosureTypeWithBindings now expands a protocol pack via packTypeArgs). Wired `tuple_type_expr` into `resolveTypeWithBindings` (type_bridge's tuple resolver is stateless — can't see packs). `packTypeArgs(pack_name, projection)` is shared: bare `..xs` → element types (`pack_arg_types`); `..xs.T` → each element's `impl Box(args) for elem` target_arg (`elementProtocolTypeArg` scans `param_impl_map`). In type position `xs.T` parses as a dotted `type_expr`, so packTypeElems splits on '.'. examples/199-pack-type-projection.sx. This completes 2.3's core: all spread/projection forms — call-arg, tuple value, tuple type, closure sig — now lower. The canonical's `Closure(..sources.T)` / `mapper(..sources.value)` / `(..sources)` shapes are functional.
This commit is contained in:
38
examples/199-pack-type-projection.sx
Normal file
38
examples/199-pack-type-projection.sx
Normal file
@@ -0,0 +1,38 @@
|
||||
// Feature 1 — TYPE-position pack projection `xs.T`. The per-element protocol
|
||||
// type-arg `T` projects into a Pack of types, usable in type/signature
|
||||
// positions: a tuple type `(..xs.T)` and a closure signature
|
||||
// `Closure(..xs.T) -> R`. (`T` of each element comes from its
|
||||
// `impl Box(T) for <elem>`.)
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: protocol(T: Type) {
|
||||
get :: () -> T;
|
||||
}
|
||||
|
||||
IntCell :: struct { v: s64; }
|
||||
StrCell :: struct { s: string; }
|
||||
Dbl :: struct { n: s64; }
|
||||
impl Box(s64) for IntCell { get :: (self: *IntCell) -> s64 => self.v; }
|
||||
impl Box(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
|
||||
impl Box(s64) for Dbl { get :: (self: *Dbl) -> s64 => self.n * 2; }
|
||||
|
||||
// Tuple type `(..xs.T)` — heterogeneous (s64, string), matched by the
|
||||
// value-projection `(..xs.get)`.
|
||||
snap :: (..xs: Box) -> void {
|
||||
t : (..xs.T) = (..xs.get);
|
||||
print("0={} 1={}\n", t.0, t.1);
|
||||
}
|
||||
|
||||
// Closure signature `Closure(..xs.T) -> s64` — here `Closure(s64, s64) -> s64`.
|
||||
// The closure literal's params are contextually typed from the projection.
|
||||
fold :: (..xs: Box) -> s64 {
|
||||
cb : Closure(..xs.T) -> s64 = (a, b) => a + b;
|
||||
return cb(xs[0].get(), xs[1].get());
|
||||
}
|
||||
|
||||
main :: () -> s32 {
|
||||
snap(IntCell.{ v = 42 }, StrCell.{ s = "hi" }); // (s64, string)
|
||||
print("fold={}\n", fold(IntCell.{ v = 10 }, Dbl.{ n = 5 })); // 10 + 10 = 20
|
||||
0;
|
||||
}
|
||||
101
src/ir/lower.zig
101
src/ir/lower.zig
@@ -10732,6 +10732,9 @@ pub const Lowering = struct {
|
||||
.function_type_expr => |ft| {
|
||||
return self.resolveFunctionTypeWithBindings(&ft);
|
||||
},
|
||||
.tuple_type_expr => |tt| {
|
||||
return self.resolveTupleTypeWithBindings(&tt);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
// Alias resolution (`ShaderHandle :: u32`, `Vec4 ::
|
||||
@@ -10752,6 +10755,14 @@ pub const Lowering = struct {
|
||||
param_ids.append(self.alloc, self.resolveTypeWithBindings(pt)) catch return .void;
|
||||
}
|
||||
if (ct.pack_name) |pn| {
|
||||
// Protocol pack (`Closure(..sources.T)` / `Closure(..sources)`):
|
||||
// expand the bound pack's per-element type-args.
|
||||
if (self.packTypeArgs(pn, ct.pack_projection)) |elems| {
|
||||
defer self.alloc.free(elems);
|
||||
for (elems) |t| param_ids.append(self.alloc, t) catch return .void;
|
||||
const ret_ty = if (ct.return_type) |rt| self.resolveTypeWithBindings(rt) else .void;
|
||||
return self.module.types.closureType(param_ids.items, ret_ty);
|
||||
}
|
||||
if (self.pack_bindings) |pb| {
|
||||
if (pb.get(pn)) |pack_tys| {
|
||||
for (pack_tys) |t| param_ids.append(self.alloc, t) catch return .void;
|
||||
@@ -10770,6 +10781,96 @@ pub const Lowering = struct {
|
||||
return self.module.types.closureType(param_ids.items, ret_ty);
|
||||
}
|
||||
|
||||
/// Resolve a tuple type expression with active pack bindings: a spread field
|
||||
/// `(..xs)` / `(..xs.T)` expands to the pack's per-element types via
|
||||
/// `packTypeElems`. Non-spread fields resolve normally.
|
||||
fn resolveTupleTypeWithBindings(self: *Lowering, tt: *const ast.TupleTypeExpr) TypeId {
|
||||
var field_ids = std.ArrayList(TypeId).empty;
|
||||
defer field_ids.deinit(self.alloc);
|
||||
for (tt.field_types) |ft| {
|
||||
if (ft.data == .spread_expr) {
|
||||
if (self.packTypeElems(ft.data.spread_expr.operand)) |elems| {
|
||||
defer self.alloc.free(elems);
|
||||
for (elems) |e| field_ids.append(self.alloc, e) catch return .void;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
field_ids.append(self.alloc, self.resolveTypeWithBindings(ft)) catch return .void;
|
||||
}
|
||||
return self.module.types.intern(.{ .tuple = .{
|
||||
.fields = self.alloc.dupe(TypeId, field_ids.items) catch return .void,
|
||||
.names = null,
|
||||
} });
|
||||
}
|
||||
|
||||
/// TYPE-position pack expansion: given a spread operand, return the
|
||||
/// per-element types. `..xs` → the pack's element types (`pack_arg_types`).
|
||||
/// `..xs.T` → each element's protocol type-arg `T` (from its
|
||||
/// `impl P(args) for elem` in `param_impl_map`). Null when not a pack spread.
|
||||
/// Caller owns the returned slice.
|
||||
fn packTypeElems(self: *Lowering, operand: *const Node) ?[]TypeId {
|
||||
if (self.pack_arg_types == null) return null;
|
||||
// In type position `xs` / `xs.T` parse to a (possibly dotted) type_expr
|
||||
// name; `field_access` covers any value-shaped form.
|
||||
var pack_name: []const u8 = "";
|
||||
var projection: ?[]const u8 = null;
|
||||
switch (operand.data) {
|
||||
.type_expr, .identifier => {
|
||||
const full = if (operand.data == .type_expr) operand.data.type_expr.name else operand.data.identifier.name;
|
||||
if (std.mem.indexOfScalar(u8, full, '.')) |dot| {
|
||||
pack_name = full[0..dot];
|
||||
projection = full[dot + 1 ..];
|
||||
} else {
|
||||
pack_name = full;
|
||||
}
|
||||
},
|
||||
.field_access => |fa| {
|
||||
pack_name = switch (fa.object.data) {
|
||||
.identifier => |id| id.name,
|
||||
.type_expr => |te| te.name,
|
||||
else => return null,
|
||||
};
|
||||
projection = fa.field;
|
||||
},
|
||||
else => return null,
|
||||
}
|
||||
return self.packTypeArgs(pack_name, projection);
|
||||
}
|
||||
|
||||
/// Per-element types for a bound protocol pack: `pack_name` alone → the
|
||||
/// element types; with `projection` (`xs.T`) → each element's protocol
|
||||
/// type-arg. Null when `pack_name` isn't a bound pack. Caller owns the slice.
|
||||
fn packTypeArgs(self: *Lowering, pack_name: []const u8, projection: ?[]const u8) ?[]TypeId {
|
||||
const pat = self.pack_arg_types orelse return null;
|
||||
const elems = pat.get(pack_name) orelse return null;
|
||||
if (projection == null) return self.alloc.dupe(TypeId, elems) catch null;
|
||||
const proto = if (self.pack_constraint) |pc| (pc.get(pack_name) orelse return null) else return null;
|
||||
const arg_idx = self.lookupProtocolArg(proto, projection.?) orelse return null;
|
||||
var out = std.ArrayList(TypeId).empty;
|
||||
for (elems) |elem| {
|
||||
out.append(self.alloc, self.elementProtocolTypeArg(proto, elem, arg_idx) orelse .void) catch return null;
|
||||
}
|
||||
return out.toOwnedSlice(self.alloc) catch null;
|
||||
}
|
||||
|
||||
/// For a concrete `elem` conforming to parameterised `proto`, return the
|
||||
/// `arg_idx`-th protocol type-arg from its `impl proto(args) for elem`
|
||||
/// (scans `param_impl_map` for `proto\x00…\x00mangle(elem)`).
|
||||
fn elementProtocolTypeArg(self: *Lowering, proto: []const u8, elem: TypeId, arg_idx: u32) ?TypeId {
|
||||
const prefix = std.fmt.allocPrint(self.alloc, "{s}\x00", .{proto}) catch return null;
|
||||
const suffix = std.fmt.allocPrint(self.alloc, "\x00{s}", .{self.mangleTypeName(elem)}) catch return null;
|
||||
var it = self.param_impl_map.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const k = entry.key_ptr.*;
|
||||
if (std.mem.startsWith(u8, k, prefix) and std.mem.endsWith(u8, k, suffix)) {
|
||||
for (entry.value_ptr.items) |impl| {
|
||||
if (arg_idx < impl.target_args.len) return impl.target_args[arg_idx];
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Resolve a `(Params...) -> Ret` function type expression with the
|
||||
/// active type/pack bindings applied. Mirrors
|
||||
/// `resolveClosureTypeWithBindings` but for `function_type_expr`.
|
||||
|
||||
1
tests/expected/199-pack-type-projection.exit
Normal file
1
tests/expected/199-pack-type-projection.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
2
tests/expected/199-pack-type-projection.txt
Normal file
2
tests/expected/199-pack-type-projection.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
0=42 1=hi
|
||||
fold=20
|
||||
Reference in New Issue
Block a user