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:
@@ -23,7 +23,7 @@ main :: () -> i32 {
|
||||
print("size_of((i32)->i32) = {}\n", size_of((i32) -> i32));
|
||||
|
||||
// Tuple literal reinterpreted as tuple type at the type-demanding site.
|
||||
print("size_of((i32, i32)) = {}\n", size_of((i32, i32)));
|
||||
print("size_of((i32, i32)) = {}\n", size_of(Tuple(i32, i32)));
|
||||
|
||||
// Aliases.
|
||||
print("size_of(Ptr) = {}\n", size_of(Ptr));
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
Box :: struct { xs: (i32, i32); }
|
||||
Box :: struct { xs: Tuple(i32, i32); }
|
||||
|
||||
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
|
||||
fst :: (t: (i64, i64)) -> i64 { t.0 }
|
||||
swap :: (a: i64, b: i64) -> Tuple(i64, i64) { .(b, a) }
|
||||
fst :: (t: Tuple(i64, i64)) -> i64 { t.0 }
|
||||
|
||||
main :: () -> i32 {
|
||||
// Inferred positional tuple + numeric field access.
|
||||
pair := (40, 2);
|
||||
pair := .(40, 2);
|
||||
print("pair {} {}\n", pair.0, pair.1);
|
||||
|
||||
// Named tuple: named + numeric access.
|
||||
named := (x: 10, y: 20);
|
||||
named := .(x = 10, y = 20);
|
||||
print("named {} {} {}\n", named.x, named.0, named.1);
|
||||
|
||||
// Element into a typed local (access path, not just print).
|
||||
@@ -27,7 +27,7 @@ main :: () -> i32 {
|
||||
|
||||
// Tuple-typed struct field: store a tuple value, read both elements.
|
||||
box : Box = ---;
|
||||
box.xs = (7, 9);
|
||||
box.xs = .(7, 9);
|
||||
print("field {} {}\n", box.xs.0, box.xs.1);
|
||||
|
||||
// Return a tuple from a function.
|
||||
@@ -35,21 +35,21 @@ main :: () -> i32 {
|
||||
print("ret {} {}\n", s.0, s.1);
|
||||
|
||||
// Pass a tuple by value.
|
||||
print("pass {}\n", fst((11, 22)));
|
||||
print("pass {}\n", fst(.(11, 22)));
|
||||
|
||||
// Operators: equality, concatenation, repetition, membership, lex.
|
||||
print("eq {}\n", (1, 2) == (1, 2));
|
||||
c := (1, 2) + (3, 4);
|
||||
print("eq {}\n", .(1, 2) == .(1, 2));
|
||||
c := .(1, 2) + .(3, 4);
|
||||
print("concat {} {}\n", c.0, c.3);
|
||||
r := (1, 2) * 3;
|
||||
r := .(1, 2) * 3;
|
||||
print("rep {} {}\n", r.0, r.5);
|
||||
print("mem {}\n", 3 in (1, 2, 3));
|
||||
print("lex {}\n", (1, 2) < (1, 3));
|
||||
print("mem {}\n", 3 in .(1, 2, 3));
|
||||
print("lex {}\n", .(1, 2) < .(1, 3));
|
||||
|
||||
// Mixed-size fields: a tuple with both an i64 and a string (16-byte fat
|
||||
// pointer). Field types are tracked per-position, so reading each back is
|
||||
// typed correctly (i64 prints as a number, string as text).
|
||||
mixed := (42, "hi");
|
||||
mixed := .(42, "hi");
|
||||
print("mixed {} {}\n", mixed.0, mixed.1);
|
||||
0
|
||||
}
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
|
||||
main :: () -> i32 {
|
||||
// Positional element assignment.
|
||||
a : (i32, string) = ---;
|
||||
a : Tuple(i32, string) = ---;
|
||||
a.0 = 11;
|
||||
a.1 = "x";
|
||||
print("a: {} {}\n", a.0, a.1);
|
||||
|
||||
// Named tuple: write + read by name, and read by position.
|
||||
p : (x: i32, y: string) = ---;
|
||||
p : Tuple(x: i32, y: string) = ---;
|
||||
p.x = 22;
|
||||
p.y = "y";
|
||||
print("p: x={} y={} .0={}\n", p.x, p.y, p.0);
|
||||
|
||||
@@ -81,26 +81,26 @@ main :: () {
|
||||
|
||||
// Basic tuple destructuring
|
||||
{
|
||||
da, db := (10, 20);
|
||||
da, db := .(10, 20);
|
||||
print("basic: {} {}\n", da, db);
|
||||
}
|
||||
|
||||
// Destructure from function return
|
||||
{
|
||||
dswap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
|
||||
dswap :: (a: i64, b: i64) -> Tuple(i64, i64) { .(b, a) }
|
||||
dx, dy := dswap(1, 2);
|
||||
print("fn: {} {}\n", dx, dy);
|
||||
}
|
||||
|
||||
// Discard with _
|
||||
{
|
||||
_, dsecond := (100, 200);
|
||||
_, dsecond := .(100, 200);
|
||||
print("discard: {}\n", dsecond);
|
||||
}
|
||||
|
||||
// Three elements
|
||||
{
|
||||
da3, db3, dc3 := (1, 2, 3);
|
||||
da3, db3, dc3 := .(1, 2, 3);
|
||||
print("triple: {} {} {}\n", da3, db3, dc3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,18 @@ main :: () {
|
||||
// --- Tuples ---
|
||||
{
|
||||
print("=== Tuples ===\n");
|
||||
pair := (40, 2);
|
||||
pair := .(40, 2);
|
||||
print("{}\n", pair.0);
|
||||
print("{}\n", pair.1);
|
||||
|
||||
named := (x: 10, y: 20);
|
||||
named := .(x = 10, y = 20);
|
||||
print("{}\n", named.x);
|
||||
print("{}\n", named.0);
|
||||
|
||||
single := (42,);
|
||||
single := .(42);
|
||||
print("{}\n", single.0);
|
||||
|
||||
zeroed : (i32, i32) = ---;
|
||||
zeroed : Tuple(i32, i32) = ---;
|
||||
print("{}\n", zeroed.0);
|
||||
print("{}\n", zeroed.1);
|
||||
}
|
||||
|
||||
@@ -44,20 +44,20 @@ main :: () {
|
||||
print("=== Tuple Operators ===\n");
|
||||
|
||||
// Equality
|
||||
print("{}\n", (1, 2) == (1, 2)); // true
|
||||
print("{}\n", (1, 2) == (1, 3)); // false
|
||||
print("{}\n", (1, 2) != (1, 3)); // true
|
||||
print("{}\n", (1, 2) != (1, 2)); // false
|
||||
print("{}\n", .(1, 2) == .(1, 2)); // true
|
||||
print("{}\n", .(1, 2) == .(1, 3)); // false
|
||||
print("{}\n", .(1, 2) != .(1, 3)); // true
|
||||
print("{}\n", .(1, 2) != .(1, 2)); // false
|
||||
|
||||
// Concatenation
|
||||
c := (1, 2) + (3, 4);
|
||||
c := .(1, 2) + .(3, 4);
|
||||
print("{}\n", c.0); // 1
|
||||
print("{}\n", c.1); // 2
|
||||
print("{}\n", c.2); // 3
|
||||
print("{}\n", c.3); // 4
|
||||
|
||||
// Repetition
|
||||
r := (1, 2) * 3;
|
||||
r := .(1, 2) * 3;
|
||||
print("{}\n", r.0); // 1
|
||||
print("{}\n", r.1); // 2
|
||||
print("{}\n", r.2); // 1
|
||||
@@ -66,16 +66,16 @@ main :: () {
|
||||
print("{}\n", r.5); // 2
|
||||
|
||||
// Lexicographic comparison
|
||||
print("{}\n", (1, 2) < (1, 3)); // true
|
||||
print("{}\n", (1, 3) < (1, 2)); // false
|
||||
print("{}\n", (1, 2) < (1, 2)); // false
|
||||
print("{}\n", (1, 2) <= (1, 2)); // true
|
||||
print("{}\n", (2, 0) > (1, 9)); // true
|
||||
print("{}\n", (1, 2) >= (1, 2)); // true
|
||||
print("{}\n", .(1, 2) < .(1, 3)); // true
|
||||
print("{}\n", .(1, 3) < .(1, 2)); // false
|
||||
print("{}\n", .(1, 2) < .(1, 2)); // false
|
||||
print("{}\n", .(1, 2) <= .(1, 2)); // true
|
||||
print("{}\n", .(2, 0) > .(1, 9)); // true
|
||||
print("{}\n", .(1, 2) >= .(1, 2)); // true
|
||||
|
||||
// Membership
|
||||
print("{}\n", 2 in (1, 2, 3)); // true
|
||||
print("{}\n", 5 in (1, 2, 3)); // false
|
||||
print("{}\n", 2 in .(1, 2, 3)); // true
|
||||
print("{}\n", 5 in .(1, 2, 3)); // false
|
||||
}
|
||||
|
||||
// --- Directory imports ---
|
||||
@@ -189,15 +189,15 @@ main :: () {
|
||||
}
|
||||
|
||||
{
|
||||
if 1 == (1,) {
|
||||
if 1 == .(1) {
|
||||
print("1 == (1)\n");
|
||||
}
|
||||
|
||||
if (1,) == (1) {
|
||||
if .(1) == (1) {
|
||||
print("(1) == 1\n");
|
||||
}
|
||||
|
||||
if (1,) == 1 {
|
||||
if .(1) == 1 {
|
||||
print("1 == 1\n");
|
||||
}
|
||||
}
|
||||
|
||||
42
examples/types/0130-types-tuple-new-syntax.sx
Normal file
42
examples/types/0130-types-tuple-new-syntax.sx
Normal file
@@ -0,0 +1,42 @@
|
||||
// New tuple syntax (additive over the legacy `(a, b)` forms):
|
||||
// - tuple TYPE `Tuple(A, B)` and named `Tuple(x: A, y: B)`
|
||||
// - tuple VALUE `.(a, b)`, named `.(x = a, y = b)`, 1-tuple `.(n)`
|
||||
// - element access by index `.0` and by name `.x`
|
||||
// - a `-> Tuple(i64, i64)` return type with a `.(b, a)` body
|
||||
// - tuple equality operator over two `.(...)` literals
|
||||
// The `Tuple(...)` type mirrors the inline `(A, B)` tuple_type_expr and
|
||||
// `.(...)` mirrors the inline `(a, b)` tuple_literal, so both self-type
|
||||
// structurally and reuse the existing tuple lowering.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
swap :: (a: i64, b: i64) -> Tuple(i64, i64) {
|
||||
.(b, a)
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
// Positional value + index access.
|
||||
p := .(1, 2);
|
||||
print("p {} {}\n", p.0, p.1);
|
||||
|
||||
// Named value (`=`) + name access.
|
||||
n := .(x = 10, y = 20);
|
||||
print("n {} {}\n", n.x, n.y);
|
||||
|
||||
// 1-tuple.
|
||||
one := .(7);
|
||||
print("one {}\n", one.0);
|
||||
|
||||
// Tuple return type with a `.(...)` body.
|
||||
s := swap(3, 4);
|
||||
print("swap {} {}\n", s.0, s.1);
|
||||
|
||||
// Named tuple TYPE annotation, filled by a named `.(...)` literal.
|
||||
nt : Tuple(x: i64, y: i64) = .(x = 5, y = 6);
|
||||
print("named-type {} {}\n", nt.x, nt.y);
|
||||
|
||||
// Tuple equality operator over two `.(...)` literals.
|
||||
print("eq {}\n", .(1, 2) == .(1, 2));
|
||||
|
||||
0
|
||||
}
|
||||
39
examples/types/0131-types-tuple-typed-construction.sx
Normal file
39
examples/types/0131-types-tuple-typed-construction.sx
Normal file
@@ -0,0 +1,39 @@
|
||||
// Explicitly-typed tuple construction `Tuple(...).( ... )` — the `Tuple(...)`
|
||||
// TYPE followed by a `.( ... )` initializer, exactly like `Point.{ ... }` for
|
||||
// structs. Symmetric trio (mirrors structs `Point` / `Point.{...}` / `.{...}`):
|
||||
// - tuple TYPE `Tuple(A, B)` (annotation / return / arg)
|
||||
// - anonymous VALUE `.(a, b)` (contextually typed)
|
||||
// - typed VALUE `Tuple(A, B).(a, b)` (explicit type + initializer)
|
||||
// A `Tuple(...).(...)` value equals the anonymous `.(...)` against that type.
|
||||
// Named forms keep `:` in the type and `=` in the value.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
// A `-> Tuple(i64, i64)` return type with a `.(b, a)` body.
|
||||
swap :: (a: i64, b: i64) -> Tuple(i64, i64) {
|
||||
.(b, a)
|
||||
}
|
||||
|
||||
main :: () -> i32 {
|
||||
// Annotation + anonymous value.
|
||||
t : Tuple(i64, i64) = .(1, 2);
|
||||
print("t = {} {}\n", t.0, t.1); // t = 1 2
|
||||
|
||||
// Explicitly-typed construction — same value as `.(3, 4)` against the type.
|
||||
u := Tuple(i64, i64).(3, 4);
|
||||
print("u = {} {}\n", u.0, u.1); // u = 3 4
|
||||
|
||||
// Named: annotation + value uses `=` for the value fields.
|
||||
p : Tuple(x: i64, y: i64) = .(x = 5, y = 6);
|
||||
print("p = {} {}\n", p.x, p.y); // p = 5 6
|
||||
|
||||
// Named: explicitly-typed construction.
|
||||
q := Tuple(x: i64, y: i64).(x = 7, y = 8);
|
||||
print("q = {} {}\n", q.x, q.y); // q = 7 8
|
||||
|
||||
// Function returning a tuple via a `.(b, a)` body.
|
||||
s := swap(10, 20);
|
||||
print("s = {} {}\n", s.0, s.1); // s = 20 10
|
||||
|
||||
0
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
// Regression (issue 0089 — attempt-2 completeness across binding forms).
|
||||
#import "modules/std.sx";
|
||||
|
||||
pair :: () -> (i64, i64) { (1, 2) }
|
||||
pair :: () -> Tuple(i64, i64) { .(1, 2) }
|
||||
maybe :: () -> ?i64 { return 42; }
|
||||
|
||||
// Function named with a reserved spelling — bare-callable (no backtick at call).
|
||||
|
||||
@@ -27,7 +27,7 @@ big_host :: () -> i32 {
|
||||
}
|
||||
|
||||
d_host :: () -> i32 {
|
||||
a, b := (1, 2);
|
||||
a, b := .(1, 2);
|
||||
print("a: {} b: {}\n", type_name(type_of(a)), type_name(type_of(b)));
|
||||
0
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ main :: () -> i32 {
|
||||
print("tag={}\n", b.tag);
|
||||
|
||||
// A tuple with a void element.
|
||||
t : (void, i32) = .{ {}, 9 };
|
||||
t : Tuple(void, i32) = .{ {}, 9 };
|
||||
print("t1={}\n", t.1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -11,24 +11,24 @@
|
||||
|
||||
main :: () {
|
||||
// Optional + float fields.
|
||||
t : (?i64, f64) = .{ 7, 3.0 };
|
||||
t : Tuple(?i64, f64) = .{ 7, 3.0 };
|
||||
print("{} {}\n", t.0 ?? -1, t.1); // 7 3.000000
|
||||
|
||||
// int -> float coercion on a tuple element.
|
||||
u : (f64, i64) = .{ 3, 4 };
|
||||
u : Tuple(f64, i64) = .{ 3, 4 };
|
||||
print("{} {}\n", u.0, u.1); // 3.000000 4
|
||||
|
||||
// Named tuple.
|
||||
n : (x: ?i64, y: f64) = .{ 5, 2.5 };
|
||||
n : Tuple(x: ?i64, y: f64) = .{ 5, 2.5 };
|
||||
print("{} {}\n", n.x ?? -1, n.y); // 5 2.500000
|
||||
|
||||
// Variable elements flowing into an optional tuple field.
|
||||
a := 9;
|
||||
b := 1.5;
|
||||
v : (?i64, f64) = .{ a, b };
|
||||
v : Tuple(?i64, f64) = .{ a, b };
|
||||
print("{} {}\n", v.0 ?? -1, v.1); // 9 1.500000
|
||||
|
||||
// A bare `null` element into an optional tuple field.
|
||||
w : (?i64, i64) = .{ null, 8 };
|
||||
w : Tuple(?i64, i64) = .{ null, 8 };
|
||||
print("{} {}\n", w.0 ?? -1, w.1); // -1 8
|
||||
}
|
||||
|
||||
@@ -29,10 +29,10 @@ main :: () {
|
||||
print("{}\n", fns[0](3, 4)); // 7
|
||||
|
||||
// A 1-tuple type still requires the trailing comma.
|
||||
one : (i64,) = (9,);
|
||||
one : Tuple(i64) = .(9);
|
||||
print("{}\n", one.0); // 9
|
||||
|
||||
// A 2-tuple is unaffected.
|
||||
two : (i64, i64) = (40, 2);
|
||||
two : Tuple(i64, i64) = .(40, 2);
|
||||
print("{}\n", two.0 + two.1); // 42
|
||||
}
|
||||
|
||||
1
examples/types/expected/0130-types-tuple-new-syntax.exit
Normal file
1
examples/types/expected/0130-types-tuple-new-syntax.exit
Normal file
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
p 1 2
|
||||
n 10 20
|
||||
one 7
|
||||
swap 4 3
|
||||
named-type 5 6
|
||||
eq true
|
||||
@@ -0,0 +1 @@
|
||||
0
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
t = 1 2
|
||||
u = 3 4
|
||||
p = 5 6
|
||||
q = 7 8
|
||||
s = 20 10
|
||||
Reference in New Issue
Block a user