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:
agra
2026-06-25 17:53:57 +03:00
parent c882c6c63e
commit 989e18b760
124 changed files with 941 additions and 1236 deletions

View File

@@ -14,7 +14,7 @@ impl Box(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
impl Box(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
snapshot :: (..xs: Box) -> void {
t := (..xs.get); // tuple (i64, string) materialized from the pack
t := .(..xs.get); // tuple (i64, string) materialized from the pack
print("0={} 1={}\n", t.0, t.1);
}

View File

@@ -20,7 +20,7 @@ impl Box(i64) for Dbl { get :: (self: *Dbl) -> i64 => self.n * 2; }
// Tuple type `(..xs.T)` — heterogeneous (i64, string), matched by the
// value-projection `(..xs.get)`.
snap :: (..xs: Box) -> void {
t : (..xs.T) = (..xs.get);
t : Tuple(..xs.T) = .(..xs.get);
print("0={} 1={}\n", t.0, t.1);
}

View File

@@ -7,19 +7,19 @@
Box :: struct($R: Type, ..$Ts: []Type) {
r: $R;
pair: (..$Ts); // tuple of the pack's element types
pair: Tuple(..$Ts); // tuple of the pack's element types
}
main :: () -> i32 {
// Box(i64, i32, string): R=i64, Ts=[i32, string], pair: (i32, string).
a : Box(i64, i32, string) = ---;
a.r = 7;
a.pair = (42, "hi"); // whole-tuple field store
a.pair = .(42, "hi"); // whole-tuple field store
print("a: r={} 0={} 1={}\n", a.r, a.pair.0, a.pair.1);
// A different shape → a different per-position tuple field.
b : Box(bool, string, bool) = ---; // Ts=[string, bool], pair: (string, bool)
b.pair = ("x", true);
b.pair = .("x", true);
print("b: 0={} 1={}\n", b.pair.0, b.pair.1);
0
}

View File

@@ -14,7 +14,7 @@ impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts)); // (VL(T0), VL(T1), …) — tuple of protocol values
sources: Tuple(..VL(Ts)); // (VL(T0), VL(T1), …) — tuple of protocol values
value: $R;
}
@@ -22,7 +22,7 @@ main :: () -> i32 {
// Combined(i64, i64, string): R=i64, Ts=[i64, string],
// sources: (VL(i64), VL(string)).
c : Combined(i64, i64, string) = ---;
c.sources = (xx IntCell.{ v = 10 }, xx StrCell.{ s = "hi" });
c.sources = .(xx IntCell.{ v = 10 }, xx StrCell.{ s = "hi" });
c.value = 99;
print("{} {} {}\n", c.sources.0.get(), c.sources.1.get(), c.value); // 10 hi 99
0

View File

@@ -11,7 +11,7 @@ IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts));
sources: Tuple(..VL(Ts));
value: $R;
}

View File

@@ -12,13 +12,13 @@ impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts));
sources: Tuple(..VL(Ts));
value: $R;
}
build :: (..sources: VL) -> void {
c : Combined(i64, ..sources.T) = ---;
c.sources = (..sources); // pack → tuple, per-element erase
c.sources = .(..sources); // pack → tuple, per-element erase
print("{} {}\n", c.sources.0.get(), c.sources.1.get());
}

View File

@@ -17,14 +17,14 @@ IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts));
sources: Tuple(..VL(Ts));
value: $R;
}
impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; }
map :: (mapper: Closure(..sources.T) -> $R, ..sources: VL) -> VL($R) {
c : Combined($R, ..sources.T) = ---;
c.sources = (..sources);
c.sources = .(..sources);
c.value = mapper(..sources.get);
return xx c;
}