feat: tuple syntax cutover — Tuple(...) type + .(...) value
Replace the bare-paren tuple grammar with explicit, position-unambiguous
forms, mirroring how structs work:
type `(A, B)` -> `Tuple(A, B)` (named keeps `:`)
value `(a, b)` -> `.(a, b)` (named uses `=`)
typed (new) -> `Tuple(A, B).(a, b)` (like `Point.{...}`)
failable `-> (T, !)` -> `-> T !`
`-> (T1, T2, !)`-> `-> Tuple(T1, T2) !` (channel outside Tuple)
Bare `(...)` is now grouping only, everywhere; a comma in bare parens is a
hard error with a migration hint. Grouping, function types `(A, B) -> R`,
param lists, lambdas, and match bindings are unaffected.
`Tuple(...)` is strictly a TYPE in every position (including `size_of` /
`type_info` args); a tuple VALUE comes only from `.(...)` (anonymous) or
`Tuple(...).(...)` (explicitly typed). A bare `Tuple(1, 2)` is a tuple
type with non-type elements -> rejected.
The ~110 tuple-bearing corpus files were migrated with a one-shot
AST-aware migrator (the `sx migrate` tool from the prior commit, removed
here). New examples: 0130 (new syntax), 0131 (typed construction), 1060
(named-tuple failable return). 1116 golden updated for the new hint text.
This commit is contained in:
@@ -950,6 +950,38 @@ pub const Lowering = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// A `Tuple(...)` element must denote a TYPE; a VALUE-literal element —
|
||||
// e.g. the `1` in `Tuple(i32, 1)` — is a user error. Diagnose it loudly
|
||||
// here (the same message the `.( ... )`-in-type path emits) BEFORE
|
||||
// `resolveCompound` would intern a tuple carrying an `.unresolved`
|
||||
// field. Only the unambiguous value literals are rejected: an
|
||||
// `error_type_expr` element (`-> Tuple(A, B) !` desugaring), names,
|
||||
// and the structural type shapes are all legitimate tuple elements.
|
||||
if (node.data == .tuple_type_expr) {
|
||||
for (node.data.tuple_type_expr.field_types) |ft| {
|
||||
// A signed numeric literal (`Tuple(i32, -1)`) arrives as a
|
||||
// `negate` unary over an int/float literal — reject it as the
|
||||
// literal it wraps, not as a generic non-type.
|
||||
const probe = if (ft.data == .unary_op and ft.data.unary_op.op == .negate)
|
||||
ft.data.unary_op.operand
|
||||
else
|
||||
ft;
|
||||
switch (probe.data) {
|
||||
.int_literal,
|
||||
.float_literal,
|
||||
.string_literal,
|
||||
.bool_literal,
|
||||
.null_literal,
|
||||
=> {
|
||||
if (self.diagnostics) |diags| {
|
||||
diags.addFmt(.err, ft.span, "tuple type element is not a type (found `{s}`); a tuple used as a type must list only types, e.g. `Tuple(i32, i32)`", .{@tagName(probe.data)});
|
||||
}
|
||||
return .unresolved;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
// Structural type shapes — `*T`, `[*]T`, `[]T`, `?T`, `[N]T`, functions,
|
||||
// PLAIN closures, and PLAIN tuples — are owned by
|
||||
// `TypeResolver.resolveCompound` (A2.3b). Element types recurse through
|
||||
|
||||
Reference in New Issue
Block a user