From 87f739cef2642703fec439ecf8f1445a5fb20971 Mon Sep 17 00:00:00 2001 From: agra Date: Fri, 29 May 2026 12:15:50 +0300 Subject: [PATCH] lang 1.1: parse pack-constrained variadic parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `..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. --- examples/probes/pack-param-parses.sx | 22 ++++++++++++ src/ast.zig | 6 ++++ src/parser.zig | 52 +++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 examples/probes/pack-param-parses.sx diff --git a/examples/probes/pack-param-parses.sx b/examples/probes/pack-param-parses.sx new file mode 100644 index 0000000..f99ade8 --- /dev/null +++ b/examples/probes/pack-param-parses.sx @@ -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]; diff --git a/src/ast.zig b/src/ast.zig index 643c886..7cc0f09 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -127,6 +127,12 @@ pub const Param = struct { type_expr: *Node, is_variadic: 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 /// parameter, lowering substitutes this expression in its place. default_expr: ?*Node = null, diff --git a/src/parser.zig b/src/parser.zig index 98e1933..bc6ebbe 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -1659,7 +1659,15 @@ pub const Parser = struct { self.advance(); // consume '=' 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| { 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.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 +}