lang 1.1: parse pack-constrained variadic parameter
`..xs: Protocol` (a bare protocol, no `[]`, no `$`) on a variadic parameter now parses to `ast.Param.is_pack = true` — a heterogeneous protocol-constrained pack, distinct from a slice variadic (`..xs: []T`, is_pack=false) and the comptime type-pack (`..$args`, is_comptime=true). Parser-only: sema/lowering for the pack form land in Phase 2; existing forms are unaffected (zero examples used a bare non-slice variadic annotation). Adds three parser unit tests and examples/probes/pack-param-parses.sx.
This commit is contained in:
22
examples/probes/pack-param-parses.sx
Normal file
22
examples/probes/pack-param-parses.sx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
// Feature 1 / Step 1.1 — pack-constrained variadic parameter PARSES.
|
||||||
|
//
|
||||||
|
// Parse-only probe. The protocol-constrained variadic `..sources: Protocol`
|
||||||
|
// (no `[]`, no `$`) now parses to an `ast.Param` with `is_pack = true`,
|
||||||
|
// distinct from a slice variadic (`..parts: []T`, `is_pack = false`) and the
|
||||||
|
// comptime type-pack (`..$args`, `is_comptime = true`). Sema/lowering for the
|
||||||
|
// pack form arrive in Phase 2 — do NOT expect this to compile/run yet.
|
||||||
|
//
|
||||||
|
// The authoritative checks are the parser unit tests in src/parser.zig
|
||||||
|
// ("parse pack-constrained variadic parameter", and the two contrast tests).
|
||||||
|
|
||||||
|
// Protocol-constrained pack (the new form):
|
||||||
|
map :: (..sources: ValueListenable) => sources;
|
||||||
|
|
||||||
|
// With a fixed prefix before the pack:
|
||||||
|
combine :: (label: string, ..sources: ValueListenable) => label;
|
||||||
|
|
||||||
|
// Contrast — slice variadic (is_pack = false):
|
||||||
|
join :: (..parts: []string) => parts;
|
||||||
|
|
||||||
|
// Contrast — comptime type pack (is_comptime = true, is_pack = false):
|
||||||
|
pick :: (..$args) => args[0];
|
||||||
@@ -127,6 +127,12 @@ pub const Param = struct {
|
|||||||
type_expr: *Node,
|
type_expr: *Node,
|
||||||
is_variadic: bool = false,
|
is_variadic: bool = false,
|
||||||
is_comptime: bool = false,
|
is_comptime: bool = false,
|
||||||
|
/// Heterogeneous protocol-constrained variadic pack: `..xs: Protocol`
|
||||||
|
/// (no `[]`, no `$`). The annotation is a bare protocol the trailing args
|
||||||
|
/// each conform to with their own type-arg — distinct from a slice variadic
|
||||||
|
/// (`..xs: []T`, `is_pack == false`) and from the comptime type-pack
|
||||||
|
/// (`..$xs`, `is_comptime == true`). Always implies `is_variadic`.
|
||||||
|
is_pack: bool = false,
|
||||||
/// Optional default value expression. When the caller omits this
|
/// Optional default value expression. When the caller omits this
|
||||||
/// parameter, lowering substitutes this expression in its place.
|
/// parameter, lowering substitutes this expression in its place.
|
||||||
default_expr: ?*Node = null,
|
default_expr: ?*Node = null,
|
||||||
|
|||||||
@@ -1659,7 +1659,15 @@ pub const Parser = struct {
|
|||||||
self.advance(); // consume '='
|
self.advance(); // consume '='
|
||||||
default_expr = try self.parseExpr();
|
default_expr = try self.parseExpr();
|
||||||
}
|
}
|
||||||
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = param_type, .is_variadic = is_variadic, .is_comptime = is_comptime_param, .default_expr = default_expr });
|
// Protocol-constrained variadic pack: `..xs: Protocol` — a bare
|
||||||
|
// type (not a slice/array) on a non-comptime variadic param. The
|
||||||
|
// trailing args each conform to the protocol with their own
|
||||||
|
// type-arg. Slice variadics (`..xs: []T`) keep `is_pack == false`.
|
||||||
|
const is_pack = is_variadic and !is_comptime_param and switch (param_type.data) {
|
||||||
|
.type_expr, .parameterized_type_expr => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
try params.append(self.allocator, .{ .name = param_name, .name_span = param_name_span, .type_expr = param_type, .is_variadic = is_variadic, .is_comptime = is_comptime_param, .is_pack = is_pack, .default_expr = default_expr });
|
||||||
}
|
}
|
||||||
for (params.items, 0..) |param, i| {
|
for (params.items, 0..) |param, i| {
|
||||||
if (param.is_variadic and i != params.items.len - 1) {
|
if (param.is_variadic and i != params.items.len - 1) {
|
||||||
@@ -3534,3 +3542,45 @@ test "integer literal overflow error" {
|
|||||||
try std.testing.expectError(error.ParseError, result);
|
try std.testing.expectError(error.ParseError, result);
|
||||||
try std.testing.expectEqualStrings("integer literal overflow", parser.err_msg.?);
|
try std.testing.expectEqualStrings("integer literal overflow", parser.err_msg.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "parse pack-constrained variadic parameter (..xs: Protocol)" {
|
||||||
|
const source = "map :: (..sources: ValueListenable) => sources;";
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
var parser = Parser.init(arena.allocator(), source);
|
||||||
|
const root = try parser.parse();
|
||||||
|
const params = root.data.root.decls[0].data.fn_decl.params;
|
||||||
|
try std.testing.expectEqual(@as(usize, 1), params.len);
|
||||||
|
const p = params[0];
|
||||||
|
try std.testing.expect(p.is_variadic);
|
||||||
|
try std.testing.expect(p.is_pack); // protocol-constrained pack
|
||||||
|
try std.testing.expect(!p.is_comptime);
|
||||||
|
try std.testing.expectEqualStrings("sources", p.name);
|
||||||
|
// The constraint is a bare type reference, not a slice.
|
||||||
|
try std.testing.expect(p.type_expr.data == .type_expr);
|
||||||
|
try std.testing.expectEqualStrings("ValueListenable", p.type_expr.data.type_expr.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse slice variadic is NOT a pack (..xs: []T)" {
|
||||||
|
const source = "join :: (..parts: []string) => parts;";
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
var parser = Parser.init(arena.allocator(), source);
|
||||||
|
const root = try parser.parse();
|
||||||
|
const p = root.data.root.decls[0].data.fn_decl.params[0];
|
||||||
|
try std.testing.expect(p.is_variadic);
|
||||||
|
try std.testing.expect(!p.is_pack); // slice variadic, not a pack
|
||||||
|
try std.testing.expect(p.type_expr.data == .slice_type_expr);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "parse comptime type-pack is NOT a protocol pack (..$args)" {
|
||||||
|
const source = "foo :: (..$args) => args;";
|
||||||
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
|
defer arena.deinit();
|
||||||
|
var parser = Parser.init(arena.allocator(), source);
|
||||||
|
const root = try parser.parse();
|
||||||
|
const p = root.data.root.decls[0].data.fn_decl.params[0];
|
||||||
|
try std.testing.expect(p.is_variadic);
|
||||||
|
try std.testing.expect(p.is_comptime); // comptime type pack
|
||||||
|
try std.testing.expect(!p.is_pack); // not the protocol-constrained form
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user