feat: parenthesized type grouping — (T) groups, (T,) is a 1-tuple (issue 0177)
In type position, parentheses now mirror value position: (T) (a single unnamed element, no trailing comma) is a GROUPING that resolves to the inner type; (T,) is a 1-tuple; (A, B) a 2-tuple; named (x: T) and spread (..Ts) stay tuples; (...) -> R stays a function type. This lets a closure/optional/function type be parenthesized for readability without silently becoming a 1-tuple: [1](Closure(i64,i64) -> i64) // array of closures (issue 0177) -> 7 ?(?i64) // genuine nested optional (issue 0165 intent) Parser: src/parser.zig returns the inner node for a single unnamed non-spread no-trailing-comma parenthesized type. formatTypeName (both generic.zig diagnostics + types.zig reflection) now render a 1-tuple as (T,) so the spelling is unambiguous and diagnostics are self-consistent. The 0165 coerce/stmt note reworded accordingly. specs.md §Type Syntax updated; basic/0036 wrap return -> (i64,); obsolete diagnostic 1195 removed (?(?i64) now compiles); regression examples/types/0201-types-parenthesized-type-grouping.sx added; 0414 .ir golden regenerated for the (T,) rendering. Resolves 0177; updates 0165/0170. Verified by 3 adversarial reviews; suite 792/0.
This commit is contained in:
@@ -703,7 +703,7 @@ pub fn coerceMode(self: *Lowering, val: Ref, src_ty: TypeId, dst_ty: TypeId, mod
|
||||
// actually IS a tuple (the `?(?T)` typo); for any other
|
||||
// mismatch the parens note would be misleading.
|
||||
const note: []const u8 = if (self.module.types.get(child_ty) == .tuple)
|
||||
" (note: in type position '(T)' is a single-field tuple, not a grouping — write the inner optional without parentheses)"
|
||||
" (note: '(T,)' with a trailing comma is a 1-tuple; '(T)' without a comma groups to the inner type)"
|
||||
else
|
||||
"";
|
||||
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "cannot wrap a value of type '{s}' into optional '{s}': its payload type is '{s}'{s}", .{ self.formatTypeName(src_ty), self.formatTypeName(dst_ty), self.formatTypeName(child_ty), note });
|
||||
|
||||
@@ -570,6 +570,10 @@ pub fn formatTypeName(self: *Lowering, ty: TypeId) []const u8 {
|
||||
}
|
||||
buf.appendSlice(self.alloc, self.formatTypeName(f)) catch break :blk "tuple";
|
||||
}
|
||||
// A 1-tuple renders with the trailing comma `(T,)` — `(T)` now means
|
||||
// a grouping (the inner type), so the comma is required to spell a
|
||||
// 1-tuple unambiguously (and keeps diagnostics self-consistent).
|
||||
if (t.fields.len == 1) buf.append(self.alloc, ',') catch break :blk "tuple";
|
||||
buf.append(self.alloc, ')') catch break :blk "tuple";
|
||||
break :blk buf.toOwnedSlice(self.alloc) catch "tuple";
|
||||
},
|
||||
|
||||
@@ -336,7 +336,7 @@ pub fn lowerVarDecl(self: *Lowering, vd: *const ast.VarDecl) void {
|
||||
// Only mention the `(T)`-is-a-1-tuple gotcha when the
|
||||
// payload actually IS a tuple (the `?(?T)` typo).
|
||||
const note: []const u8 = if (self.module.types.get(child) == .tuple)
|
||||
" (note: in type position '(T)' is a single-field tuple, not a grouping — write the inner optional without parentheses)"
|
||||
" (note: '(T,)' with a trailing comma is a 1-tuple; '(T)' without a comma groups to the inner type)"
|
||||
else
|
||||
"";
|
||||
d.addFmt(.err, ast.Span{ .start = cs.start, .end = cs.end }, "cannot assign a value of type '{s}' to optional '{s}': its payload type is '{s}'{s}", .{ self.formatTypeName(post_rt), self.formatTypeName(ty), self.formatTypeName(child), note });
|
||||
|
||||
@@ -1117,6 +1117,8 @@ pub const TypeTable = struct {
|
||||
if (i > 0) buf.appendSlice(alloc, ", ") catch break :blk "(?)";
|
||||
buf.appendSlice(alloc, self.formatTypeName(alloc, f)) catch break :blk "(?)";
|
||||
}
|
||||
// 1-tuple renders `(T,)` — `(T)` now spells a grouping.
|
||||
if (tu.fields.len == 1) buf.append(alloc, ',') catch break :blk "(?)";
|
||||
buf.append(alloc, ')') catch break :blk "(?)";
|
||||
break :blk buf.toOwnedSlice(alloc) catch "(?)";
|
||||
},
|
||||
|
||||
@@ -552,10 +552,17 @@ pub const Parser = struct {
|
||||
// An error channel type (`!` / `!Named`) is only valid as the
|
||||
// trailing element of a result list. Reject any element after it.
|
||||
var saw_error_type = false;
|
||||
// Track an explicit trailing comma so a single-element `(T,)` stays a
|
||||
// 1-tuple while `(T)` (no comma) is a GROUPING — see the grouping
|
||||
// return below.
|
||||
var had_trailing_comma = false;
|
||||
while (self.current.tag != .r_paren and self.current.tag != .eof) {
|
||||
if (param_types.items.len > 0) {
|
||||
try self.expect(.comma);
|
||||
if (self.current.tag == .r_paren) break; // trailing comma ok
|
||||
if (self.current.tag == .r_paren) {
|
||||
had_trailing_comma = true;
|
||||
break; // trailing comma ok
|
||||
}
|
||||
}
|
||||
if (saw_error_type) {
|
||||
return self.fail("error type '!' must be the last element of a result list");
|
||||
@@ -601,9 +608,21 @@ pub const Parser = struct {
|
||||
.abi = abi,
|
||||
} });
|
||||
}
|
||||
// No '->': tuple type (even for single element). Keep field names
|
||||
// for a named tuple `(x: T, y: U)` so `t.x` resolves. `field_names`
|
||||
// is non-optional per slot, so synthesize `_<i>` for any unnamed one.
|
||||
// No '->': GROUPING vs tuple. Mirror value position (`(expr)` groups,
|
||||
// `(expr,)` is a 1-tuple): a single UNNAMED, non-spread element with
|
||||
// NO trailing comma is a grouping — resolve to the inner type. This
|
||||
// lets `(Closure(i64,i64) -> i64)`, `?(?i64)`, etc. parenthesize a
|
||||
// type for grouping/readability. A 1-tuple type now requires the
|
||||
// trailing comma `(T,)`; named `(x: T)` and spread `(..Ts)` stay
|
||||
// tuples.
|
||||
if (param_types.items.len == 1 and !had_trailing_comma and !has_names and
|
||||
param_types.items[0].data != .spread_expr)
|
||||
{
|
||||
return param_types.items[0];
|
||||
}
|
||||
// Tuple type. Keep field names for a named tuple `(x: T, y: U)` so
|
||||
// `t.x` resolves. `field_names` is non-optional per slot, so
|
||||
// synthesize `_<i>` for any unnamed one.
|
||||
var field_names: ?[]const []const u8 = null;
|
||||
if (has_names) {
|
||||
var fns = std.ArrayList([]const u8).empty;
|
||||
|
||||
Reference in New Issue
Block a user