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

@@ -28,14 +28,14 @@ main :: () {
print("{}\n", 1 |> calc(2, 3, 4)); // same = 3 — pipe UFCS
// Tuple return type
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) }
swap :: (a: i64, b: i64) -> Tuple(i64, i64) { .(b, a) }
s := swap(1, 2);
a := s.0;
b := s.1;
print("{}\n", a); // 2
print("{}\n", b); // 1
wrap :: (x: i64) -> (i64,) { (x,) } // 1-tuple needs trailing comma; (i64) groups
wrap :: (x: i64) -> Tuple(i64) { .(x) } // 1-tuple needs trailing comma; (i64) groups
t := wrap(99);
print("{}\n", t.0); // 99
}

View File

@@ -17,7 +17,7 @@ E :: error { Neg }
const_one :: () -> i64 { return 1; return 99; }
// dead `return x;` after an unconditional raise (the failable closure shape)
always_raise :: (x: i64) -> (i64, !E) { raise error.Neg; return x; }
always_raise :: (x: i64) -> i64 !E { raise error.Neg; return x; }
// guard: a conditional return must still fall through to the trailing return
clamp :: (x: i64) -> i64 { if x > 10 { return 10; } return x; }

View File

@@ -8,7 +8,7 @@
#import "modules/std.sx";
pair :: () -> (i32, i32) { (5, 7) }
pair :: () -> Tuple(i32, i32) { .(5, 7) }
main :: () -> i32 {
// destructure decl inside a value-bound block

View File

@@ -11,7 +11,7 @@
Pair :: define(declare("Pair"), .tuple(.{ elements = .[ i64, f64 ] }));
TripleCopy :: define(declare("TripleCopy"), type_info((i64, bool, f64)));
TripleCopy :: define(declare("TripleCopy"), type_info(Tuple(i64, bool, f64)));
main :: () -> i32 {
p : Pair = .{ 3, 2.5 };

View File

@@ -2,6 +2,6 @@
// `error: field 'N' not found on type '(i64, i64)'` diagnostic and exit 1.
main :: () -> i32 {
t := (10, 20);
t := .(10, 20);
return xx t.42;
}

View File

@@ -8,6 +8,6 @@
#import "modules/std.sx";
main :: () -> i32 {
print("bad tuple type size = {}\n", size_of((i32, 1)));
print("bad tuple type size = {}\n", size_of(Tuple(i32, 1)));
0
}

View File

@@ -11,7 +11,7 @@
// offending name; exit 1 — NOT an LLVM verifier abort.
#import "modules/std.sx";
pair :: () -> (i64, i64) { (1, 2) }
pair :: () -> Tuple(i64, i64) { .(1, 2) }
maybe :: () -> ?i64 { return null; }
main :: () -> i32 {

View File

@@ -1,6 +1,6 @@
#import "modules/std.sx";
pair :: () -> (i64, i64) { (1, 2) }
pair :: () -> Tuple(i64, i64) { .(1, 2) }
run :: () -> i32 {
i2, rest := pair(); // destructure name in an IMPORTED module

View File

@@ -6,7 +6,7 @@
E :: error { Bad };
f :: () -> (i64, !E) { raise error.Bad; }
f :: () -> i64 !E { raise error.Bad; }
main :: () {
v := f() catch e { 0 };

View File

@@ -8,7 +8,7 @@
#import "modules/std.sx";
main :: () {
o : ?(i32,) = null;
o : ?Tuple(i32) = null;
x := o ?? 5; // default 'i64' vs payload '(i32,)' -> diagnostic
print("{}\n", x.0);
}

View File

@@ -1,5 +1,5 @@
error: tuple type element is not a type (found `int_literal`); a tuple used as a type must list only types, e.g. `(i32, i32)`
--> examples/diagnostics/1116-diagnostics-tuple-type-nontype-element-rejected.sx:11:55
error: tuple type element is not a type (found `int_literal`); a tuple used as a type must list only types, e.g. `Tuple(i32, i32)`
--> examples/diagnostics/1116-diagnostics-tuple-type-nontype-element-rejected.sx:11:60
|
11 | print("bad tuple type size = {}\n", size_of((i32, 1)));
| ^
11 | print("bad tuple type size = {}\n", size_of(Tuple(i32, 1)));
| ^

View File

@@ -10,7 +10,7 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 10; // success → {n*10, 0}

View File

@@ -10,14 +10,14 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 2;
}
// value-carrying `try` in a value-carrying caller — propagates {undef, tag}.
inc :: (n: i32) -> (i32, !E) {
inc :: (n: i32) -> i32 !E {
v := try parse(n);
return v + 1;
}

View File

@@ -8,7 +8,7 @@
E :: error { Bad }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
return n;
}

View File

@@ -9,7 +9,7 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 2;

View File

@@ -12,37 +12,37 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, i32, !E) {
parse :: (n: i32) -> Tuple(i32, i32) !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return (n * 2, n + 1); // success → {n*2, n+1, 0}
return .(n * 2, n + 1); // success → {n*2, n+1, 0}
}
// Multi-value `try` in a multi-value caller — propagates {undef, undef, tag}.
inc :: (n: i32) -> (i32, i32, !E) {
inc :: (n: i32) -> Tuple(i32, i32) !E {
v, b := try parse(n);
return (v + 1, b + 1);
return .(v + 1, b + 1);
}
// Multi-value `catch`, bare-expression tuple fallback (absorbs the failure).
safe :: (n: i32) -> i32 {
v, b := parse(n) catch (e) (40, 50);
v, b := parse(n) catch (e) .(40, 50);
return v + b;
}
// Multi-value `catch` match-body — per-tag dispatch, each arm a value-tuple.
classify :: (n: i32) -> i32 {
v, b := parse(n) catch (e) == {
case .Bad: (1, 1);
case .Empty: (2, 2);
else: (9, 9);
case .Bad: .(1, 1);
case .Empty: .(2, 2);
else: .(9, 9);
};
return v + b;
}
// Multi-value `or (tuple)` value-terminator (absorbs the failure).
ortest :: (n: i32) -> i32 {
v, b := parse(n) or (7, 8);
v, b := parse(n) or .(7, 8);
return v + b;
}

View File

@@ -13,12 +13,12 @@
E :: error { Bad, Empty }
pair :: (n: i32) -> (i32, i32, !E) {
pair :: (n: i32) -> Tuple(i32, i32) !E {
if n < 0 { raise error.Bad; }
return (n, n + 1);
return .(n, n + 1);
}
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
return n * 2;
}

View File

@@ -8,7 +8,7 @@
E :: error { BadDigit, Empty, Overflow }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.BadDigit; }
if n == 0 { raise error.Empty; }
return n * 2;

View File

@@ -14,7 +14,7 @@
ParseErr :: error { Empty, BadDigit };
inner :: (n: i32) -> (i32, !ParseErr) {
inner :: (n: i32) -> i32 !ParseErr {
if n == 0 { raise error.Empty; } // pushes a frame
if n < 0 { raise error.BadDigit; }
return n * 2;

View File

@@ -9,13 +9,13 @@
ParseErr :: error { Empty, BadDigit };
inner :: (n: i32) -> (i32, !ParseErr) {
inner :: (n: i32) -> i32 !ParseErr {
if n == 0 { raise error.Empty; }
if n < 0 { raise error.BadDigit; }
return n * 2;
}
main :: () -> (i32, !ParseErr) {
main :: () -> i32 !ParseErr {
v := try inner(32); // succeeds → v = 64
print("v = {}\n", v);
return v; // success → exit code 64

View File

@@ -12,7 +12,7 @@
E :: error { A, B };
fa :: (n: i32) -> (i32, !E) {
fa :: (n: i32) -> i32 !E {
if n == 0 { raise error.A; }
if n < 0 { raise error.B; }
return n;
@@ -23,7 +23,7 @@ fv :: (n: i32) -> !E { // void (pure) failable
return;
}
main :: () -> (i32, !E) {
main :: () -> i32 !E {
onfail print("onfail fired (BUG)\n"); // must NOT fire — every chain below absorbs
r : i32 = 0;

View File

@@ -10,12 +10,12 @@
E :: error { A };
fa :: (n: i32) -> (i32, !E) {
fa :: (n: i32) -> i32 !E {
if n == 0 { raise error.A; }
return n;
}
main :: () -> (i32, !E) {
main :: () -> i32 !E {
v := try fa(0) or try fa(0) or try fa(0); // all fail → propagate to main
return v;
}

View File

@@ -9,7 +9,7 @@
SmokeErr :: error { Empty, BadDigit, Overflow }
// value-carrying, named set
sm_parse :: (n: i32) -> (i32, !SmokeErr) {
sm_parse :: (n: i32) -> i32 !SmokeErr {
if n < 0 { raise error.BadDigit; }
if n == 0 { raise error.Empty; }
if n > 99 { raise error.Overflow; }
@@ -23,10 +23,10 @@ sm_check :: (ok: bool) -> ! {
}
// multi-value, inferred set: `try` propagates; SCC absorbs SmokeErr
sm_pair :: (a: i32, b: i32) -> (i32, i32, !) {
sm_pair :: (a: i32, b: i32) -> Tuple(i32, i32) ! {
x := try sm_parse(a);
y := try sm_parse(b);
return (x, y);
return .(x, y);
}
// catch with a diverging block body
@@ -38,7 +38,7 @@ sm_or_default :: (n: i32) -> i32 {
}
// onfail + defer interleave: cleanup runs only on the error path
sm_acquire :: (fail: bool) -> (i32, !) {
sm_acquire :: (fail: bool) -> i32 ! {
defer print(" defer A\n");
onfail print(" onfail B\n");
if fail { raise error.Acquire; }
@@ -46,7 +46,7 @@ sm_acquire :: (fail: bool) -> (i32, !) {
}
// or-chain: try a, fall to try b; propagate if both fail
sm_first :: (a: i32, b: i32) -> (i32, !) {
sm_first :: (a: i32, b: i32) -> i32 ! {
v := try sm_parse(a) or try sm_parse(b);
return v;
}
@@ -54,18 +54,18 @@ sm_first :: (a: i32, b: i32) -> (i32, !) {
// --- Composition (ERR E5.1): failable closures, widening, generics ---
// Closure(...) param, try-propagated (the env is carried)
sm_run :: (cb: Closure(i32) -> (i32, !SmokeErr), n: i32) -> (i32, !SmokeErr) {
sm_run :: (cb: Closure(i32) -> i32 !SmokeErr, n: i32) -> i32 !SmokeErr {
return try cb(n);
}
// bare fn-type param: a NON-failable closure literal widens into the failable
// slot (the ∅-widening adapter wraps `{value, 0}`)
sm_widen :: (cb: (i32) -> (i32, !SmokeErr), n: i32) -> i32 {
sm_widen :: (cb: (i32) -> i32 !SmokeErr, n: i32) -> i32 {
return cb(n) catch (e) -1;
}
// generic ($T) value-carrying failable composition, monomorphized per call
sm_wrap :: ($T: Type, f: Closure() -> (T, !SmokeErr)) -> (T, !SmokeErr) {
sm_wrap :: ($T: Type, f: Closure() -> T !SmokeErr) -> T !SmokeErr {
return try f();
}
@@ -105,9 +105,9 @@ main :: () {
if !gerr { print("or-chain: {}\n", g); }
// multi-value failable consumed by catch (tuple body)
p, q := sm_pair(0, 3) catch (e) (0, 0);
p, q := sm_pair(0, 3) catch (e) .(0, 0);
print("pair-catch: {} {}\n", p, q);
p2, q2 := sm_pair(4, 5) catch (e) (0, 0);
p2, q2 := sm_pair(4, 5) catch (e) .(0, 0);
print("pair-ok: {} {}\n", p2, q2);
// pure failable: absorb with no-binding catch
@@ -120,15 +120,15 @@ main :: () {
iv, ierr := sm_acquire(false);
// composition: inline failable closure literal through a Closure(...) param
cl := sm_run(closure((x: i32) -> (i32, !SmokeErr) { if x < 0 { raise error.BadDigit; } return x * 2; }), 6) catch (e) -1;
cl := sm_run(closure((x: i32) -> i32 !SmokeErr { if x < 0 { raise error.BadDigit; } return x * 2; }), 6) catch (e) -1;
print("closure-run: {}\n", cl); // 12
print("closure-run-err: {}\n", sm_run(closure((x: i32) -> (i32, !SmokeErr) { raise error.Empty; }), 1) catch (e) -9); // -9
print("closure-run-err: {}\n", sm_run(closure((x: i32) -> i32 !SmokeErr { raise error.Empty; }), 1) catch (e) -9); // -9
// non-failable closure literal widened into the failable bare slot
print("widen: {}\n", sm_widen(closure((x: i32) -> i32 => x + 1), 9)); // 10
// generic failable composition (monomorphized at i32)
print("wrap: {}\n", sm_wrap(i32, closure(() -> (i32, !SmokeErr) { return 42; })) catch (e) 0); // 42
print("wrap: {}\n", sm_wrap(i32, closure(() -> i32 !SmokeErr { return 42; })) catch (e) 0); // 42
print("errors ok\n");
}

View File

@@ -7,7 +7,7 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 2;

View File

@@ -7,7 +7,7 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 2;

View File

@@ -10,12 +10,12 @@
E :: error { Neg }
runwith :: (cb: Closure(i64) -> (i64, !E), n: i64) -> i64 { return cb(n) catch (e) -1; }
runwith :: (cb: Closure(i64) -> i64 !E, n: i64) -> i64 { return cb(n) catch (e) -1; }
main :: () -> i32 {
// block-body and arrow-body failable closures, called directly
m := closure((x: i64) -> (i64, !E) { if x < 0 { raise error.Neg; } return x * 2; });
n := closure((x: i64) -> (i64, !E) => x + 1);
m := closure((x: i64) -> i64 !E { if x < 0 { raise error.Neg; } return x * 2; });
n := closure((x: i64) -> i64 !E => x + 1);
print("{} {} {} {}\n", m(5) catch (e) 0, m(-1) catch (e) 99, m(-1) or 7, n(40) catch (e) 0); // 10 99 7 41
// failable closure passed as a Closure(...) parameter

View File

@@ -12,21 +12,21 @@
E :: error { Neg }
bare :: (cb: (i64) -> (i64, !E), n: i64) -> i64 { return cb(n) catch (e) -1; }
chain :: (cb: Closure(i64) -> (i64, !E), n: i64) -> (i64, !E) { return try cb(n); }
bare :: (cb: (i64) -> i64 !E, n: i64) -> i64 { return cb(n) catch (e) -1; }
chain :: (cb: Closure(i64) -> i64 !E, n: i64) -> i64 !E { return try cb(n); }
dbl :: (x: i64) -> (i64, !E) { if x < 0 { raise error.Neg; } return x * 2; }
dbl :: (x: i64) -> i64 !E { if x < 0 { raise error.Neg; } return x * 2; }
main :: () -> i32 {
// failable closure literal through a bare fn-type param (matching ABI)
print("bare ok={} err={}\n",
bare(closure((x: i64) -> (i64, !E) { if x < 0 { raise error.Neg; } return x * 2; }), 5),
bare(closure((x: i64) -> (i64, !E) => x * 2), -1)); // ok=10; err: arrow never raises → cb(-1) = -2
bare(closure((x: i64) -> i64 !E { if x < 0 { raise error.Neg; } return x * 2; }), 5),
bare(closure((x: i64) -> i64 !E => x * 2), -1)); // ok=10; err: arrow never raises → cb(-1) = -2
// Closure(...) param, try-propagated, then caught at the call site
print("chain ok={} err={}\n",
chain(closure((x: i64) -> (i64, !E) => x + 6), 4) catch (e) 0, // 10
chain(closure((x: i64) -> (i64, !E) { raise error.Neg; }), 1) catch (e) 0); // 0
chain(closure((x: i64) -> i64 !E => x + 6), 4) catch (e) 0, // 10
chain(closure((x: i64) -> i64 !E { raise error.Neg; }), 1) catch (e) 0); // 0
// NON-failable closure literal widened into the failable bare slot
print("widen={}\n", bare(closure((x: i64) -> i64 => x + 1), 9)); // 10

View File

@@ -10,7 +10,7 @@
All :: error { Negative, Other }
// `h` is a bare-`!` Closure slot; the caller declares the union as `!All`.
dispatch :: (h: Closure(i32) -> (i32, !), x: i32) -> (i32, !All) {
dispatch :: (h: Closure(i32) -> i32 !, x: i32) -> i32 !All {
return try h(x);
}
@@ -19,9 +19,9 @@ main :: () -> i32 {
push Context.{ allocator = xx gpa } {
// Two literals of the SAME shape raising DIFFERENT tags both feed the
// one shared `Closure(i32)->(i32,!)` union node.
handlers : List(Closure(i32) -> (i32, !)) = .{};
handlers.append(closure((x: i32) -> (i32, !) { if x < 0 { raise error.Negative; } return x * 2; }));
handlers.append(closure((x: i32) -> (i32, !) { if x == 0 { raise error.Other; } return x + 100; }));
handlers : List(Closure(i32) -> i32 !) = .{};
handlers.append(closure((x: i32) -> i32 ! { if x < 0 { raise error.Negative; } return x * 2; }));
handlers.append(closure((x: i32) -> i32 ! { if x == 0 { raise error.Other; } return x + 100; }));
// success paths
print("ok0={}\n", dispatch(handlers.items[0], 5) catch (e) 0); // 10

View File

@@ -9,16 +9,16 @@
Small :: error { Unrelated }
reject :: (h: Closure(i32) -> (i32, !), x: i32) -> (i32, !Small) {
reject :: (h: Closure(i32) -> i32 !, x: i32) -> i32 !Small {
return try h(x); // Negative, Other ∉ Small → two diagnostics
}
main :: () -> i32 {
gpa := GPA.init();
push Context.{ allocator = xx gpa } {
handlers : List(Closure(i32) -> (i32, !)) = .{};
handlers.append(closure((x: i32) -> (i32, !) { if x < 0 { raise error.Negative; } return x; }));
handlers.append(closure((x: i32) -> (i32, !) { if x == 0 { raise error.Other; } return x; }));
handlers : List(Closure(i32) -> i32 !) = .{};
handlers.append(closure((x: i32) -> i32 ! { if x < 0 { raise error.Negative; } return x; }));
handlers.append(closure((x: i32) -> i32 ! { if x == 0 { raise error.Other; } return x; }));
print("r={}\n", reject(handlers.items[0], 5) catch (e) 0);
}
return 0;

View File

@@ -9,7 +9,7 @@
E :: error { Neg }
take :: (cb: Closure(i32) -> (i32, !E), x: i32) -> i32 { return cb(x) catch (e) -1; }
take :: (cb: Closure(i32) -> i32 !E, x: i32) -> i32 { return cb(x) catch (e) -1; }
main :: () -> i32 {
// `-> i32` (non-failable) but the body raises → lambda-specific hint:

View File

@@ -9,21 +9,21 @@
E :: error { Bad }
wrap :: ($T: Type, f: Closure() -> (T, !E)) -> (T, !E) { return try f(); }
wrap :: ($T: Type, f: Closure() -> T !E) -> T !E { return try f(); }
main :: () -> i32 {
// success, consumed by catch
print("catch={}\n", wrap(i32, closure(() -> (i32, !E) { return 7; })) catch (e) -1); // 7
print("catch={}\n", wrap(i32, closure(() -> i32 !E { return 7; })) catch (e) -1); // 7
// success, consumed by destructure (binds value + error slot); the value
// slot is read only under an `if !err` guard (ERR E1.8 path-sensitivity)
r, err := wrap(i32, closure(() -> (i32, !E) { return 9; }));
r, err := wrap(i32, closure(() -> i32 !E { return 9; }));
if !err { print("destr={} ok=true\n", r); } // destr=9 ok=true
// failure path: the raised tag propagates through the generic `try`
print("fail={}\n", wrap(i32, closure(() -> (i32, !E) { raise error.Bad; }) ) catch (e) -1); // -1
print("fail={}\n", wrap(i32, closure(() -> i32 !E { raise error.Bad; }) ) catch (e) -1); // -1
// a second monomorphization at a different T
print("u8={}\n", wrap(u8, closure(() -> (u8, !E) { return 200; })) catch (e) 0); // 200
print("u8={}\n", wrap(u8, closure(() -> u8 !E { return 200; })) catch (e) 0); // 200
return 0;
}

View File

@@ -13,7 +13,7 @@
E :: error { Z }
bare :: (cb: (i64) -> i64, n: i64) -> i64 { return cb(n); }
baref :: (cb: (i64) -> (i64, !E), n: i64) -> i64 { return cb(n) catch (e) -1; }
baref :: (cb: (i64) -> i64 !E, n: i64) -> i64 { return cb(n) catch (e) -1; }
main :: () -> i32 {
inc := closure((x: i64) -> i64 => x + 1); // capture-free closure var

View File

@@ -15,7 +15,7 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 10;
@@ -29,7 +29,7 @@ guarded :: (n: i32) -> i32 {
}
// `if err { raise }` in a failable function: same fall-through proof.
relay :: (n: i32) -> (i32, !E) {
relay :: (n: i32) -> i32 !E {
v, err := parse(n);
if err { raise err; }
return v + 1; // err proven absent here

View File

@@ -11,7 +11,7 @@
E :: error { Bad }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
return n * 10;
}

View File

@@ -9,7 +9,7 @@
E :: error { Bad }
failing :: () -> !E { raise error.Bad; }
recover :: () -> (i32, !E) { raise error.Bad; }
recover :: () -> i32 !E { raise error.Bad; }
work :: (n: i32) -> !E {
defer print("defer: always\n"); // plain cleanup

View File

@@ -9,7 +9,7 @@
E :: error { Bad }
probe :: () -> (i32, !E) { return 21; }
probe :: () -> i32 !E { return 21; }
failing :: () -> !E { raise error.Bad; }
run :: () {

View File

@@ -24,7 +24,7 @@
E :: error { Bad }
failing :: () -> !E { raise error.Bad; }
recover :: () -> (i32, !E) { return 21; }
recover :: () -> i32 !E { return 21; }
work :: () {
defer {

View File

@@ -12,7 +12,7 @@
E :: error { Bad }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
return n * 10;
}

View File

@@ -8,7 +8,7 @@
E :: error { Bad, Empty }
parse :: (n: i32) -> (i32, !E) {
parse :: (n: i32) -> i32 !E {
if n < 0 { raise error.Bad; }
if n == 0 { raise error.Empty; }
return n * 2;

View File

@@ -16,7 +16,7 @@
Color :: enum { red; green; blue; }
E :: error { Nope }
pick :: (s: string) -> (Color, !E) {
pick :: (s: string) -> Color !E {
if s == "red" { return .red; }
if s == "blue" { return .blue; } // non-zero ordinal (2)
raise error.Nope;

View File

@@ -22,13 +22,13 @@ Color :: enum { red; green; blue; }
E :: error { Nope }
// F1: bare-value success path AND explicit-tuple error path in one function.
classify :: (s: string) -> (Color, !E) {
classify :: (s: string) -> Color !E {
if s == "ok" { return .blue; } // bare value → {2, 0}
return (.red, error.Nope); // explicit full tuple → {0, 1}
return .(.red, error.Nope); // explicit full tuple → {0, 1}
}
// F2: comptime parameter forces inline lowering of the body.
ct_pick :: ($n: i32, s: string) -> (Color, !E) {
ct_pick :: ($n: i32, s: string) -> Color !E {
if s == "red" { return .red; } // bare value, inline path → {0, 0}
if s == "blue" { return .blue; } // bare value, inline path → {2, 0}
raise error.Nope; // inline error path → {undef, 1}

View File

@@ -7,7 +7,7 @@
E :: error { Boom }
f :: (fail: bool) -> (i64, !E) {
f :: (fail: bool) -> i64 !E {
if fail { raise error.Boom; }
return 42;
}

View File

@@ -5,4 +5,4 @@ LE :: error { Bad }
Box :: struct ($R: Type) { v: R; }
// Returns `($R, !LE)` — a value-failable. `$R` is inferred from the arg.
get :: ufcs (b: *Box($R)) -> ($R, !LE) { return b.v; }
get :: ufcs (b: *Box($R)) -> $R !LE { return b.v; }

View File

@@ -0,0 +1,26 @@
// A failable function returning a NAMED tuple value `-> Tuple(x: A, y: B) !E`
// flattens its value fields into the result tuple (`{ x: A, y: B, err }`),
// keeping the `.x`/`.y` names addressable on both the success value and the
// `or` fallback. Exercises the success path, a `raise` path, and an
// `or .(x=.., y=..)` terminator.
//
// Regression (issue 0179-adjacent named-tuple-failable miscompile): a named
// failable tuple used to WRAP as `{ {A,B}, err }` while the value-return
// lowering inserted the value slots FLAT, producing invalid LLVM
// (`Invalid InsertValueInst operands`).
#import "modules/std.sx";
E :: error { Bad }
two :: (n: i64) -> Tuple(x: i64, y: i64) !E {
if n < 0 { raise error.Bad; }
return .(x = n, y = n + 1);
}
main :: () {
ok := two(5) or .(x = 0, y = 0);
print("{} {}\n", ok.x, ok.y);
bad := two(-1) or .(x = 0, y = 0);
print("{} {}\n", bad.x, bad.y);
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,2 @@
5 6
0 0

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;
}

View File

@@ -5,7 +5,7 @@
// `.build`: ir-only on a non-x86 host (the `.ir` snapshot locks the struct
// return + `%[name]` rewrite); runs natively on x86_64-linux. See 1647 for a
// multi-output example that executes on aarch64.
divmod :: (n: u64, d: u64) -> (quot: u64, rem: u64) {
divmod :: (n: u64, d: u64) -> Tuple(quot: u64, rem: u64) {
return asm {
"divq %[d]",
[quot] "={rax}" -> u64,

View File

@@ -3,7 +3,7 @@
// a `(lo, hi)` tuple. The two outputs become an LLVM `{ i64, i64 }` struct =
// sx's tuple. aarch64-pinned via `.build`: executes on a matching host (exit
// reflects lo+hi), ir-only elsewhere.
split :: (x: u64) -> (lo: u64, hi: u64) {
split :: (x: u64) -> Tuple(lo: u64, hi: u64) {
return asm {
#string ASM
and %[l], %[x], #0xff

View File

@@ -7,13 +7,13 @@
// checks are the parser unit tests in src/parser.zig ("parse pack expansion: …").
// 1. Tuple value position — `(..pack)` / `(..pack.field)`:
tv1 :: () => (..xs);
tv2 :: () => (..xs.value);
tv3 :: () => (a, ..xs, b); // mixed positional + spread
tv1 :: () => .(..xs);
tv2 :: () => .(..xs.value);
tv3 :: () => .(a, ..xs, b); // mixed positional + spread
// 2. Tuple type position — `(..F(Ts))` / `(..F(Ts.Arg))`:
tt1 :: (x: (..ValueListenable(Ts))) => x;
tt2 :: (x: (..ValueListenable(Ts.Arg))) => x;
tt1 :: (x: Tuple(..ValueListenable(Ts))) => x;
tt2 :: (x: Tuple(..ValueListenable(Ts.Arg))) => x;
// 3. Call-arg position — `..pack` / `..pack.field` (reuses spread_expr):
ca1 :: () => f(..xs);

View File

@@ -11,35 +11,35 @@
#import "modules/std.sx";
Listenable :: struct { value: i64; } // stand-in element struct
Combined :: struct { sources: (i32, i32); } // tuple-typed field (Decision 2)
Combined :: struct { sources: Tuple(i32, i32); } // tuple-typed field (Decision 2)
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 {
// ── Block A — primitives (WORKS) ───────────────────────────────
pair := (40, 2); // inferred positional
pair := .(40, 2); // inferred positional
print("A.idx {} {}\n", pair.0, pair.1);
named := (x: 10, y: 20); // named + numeric access
named := .(x = 10, y = 20); // named + numeric access
print("A.named {} {} {}\n", named.x, named.0, named.1);
one := (42,); // 1-tuple
one := .(42); // 1-tuple
print("A.one {}\n", one.0);
a : i64 = pair.0; // element into typed local
print("A.local {}\n", a);
// ── Block B — storage in a struct field (WORKS; core of Decision 2)
c : Combined = ---;
c.sources = (7, 9); // assign tuple value to field
c.sources = .(7, 9); // assign tuple value to field
print("B.field {} {}\n", c.sources.0, c.sources.1);
// ── Block C — return / pass / operators (WORKS) ────────────────
s := swap(1, 2);
print("C.ret {} {}\n", s.0, s.1);
print("C.pass {}\n", fst((11, 22)));
print("C.eq {}\n", (1, 2) == (1, 2));
cc := (1, 2) + (3, 4);
print("C.pass {}\n", fst(.(11, 22)));
print("C.eq {}\n", .(1, 2) == .(1, 2));
cc := .(1, 2) + .(3, 4);
print("C.concat {} {}\n", cc.0, cc.3);
print("C.mem {}\n", 3 in (1, 2, 3));
print("C.mem {}\n", 3 in .(1, 2, 3));
0
}

View File

@@ -14,7 +14,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;
}
impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; }
@@ -22,7 +22,7 @@ impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.val
make :: (..sources: VL) -> VL(i64) {
c : Combined(i64, ..sources.T) = ---;
c.value = 99;
c.sources = (..sources);
c.sources = .(..sources);
return xx c; // Combined__i64_i64 -> VL(i64)
}

View File

@@ -141,7 +141,7 @@ SmokeErr :: error { Empty, BadDigit, Overflow }
// value-carrying, named set: raise three tags or succeed
// value-carrying, named set: raise three tags or succeed
sm_parse :: (n: i32) -> (i32, !SmokeErr) {
sm_parse :: (n: i32) -> i32 !SmokeErr {
if n < 0 { raise error.BadDigit; }
if n == 0 { raise error.Empty; }
if n > 99 { raise error.Overflow; }
@@ -159,10 +159,10 @@ sm_check :: (ok: bool) -> ! {
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
// multi-value, inferred set: `try` propagates; the SCC pass absorbs SmokeErr
sm_pair :: (a: i32, b: i32) -> (i32, i32, !) {
sm_pair :: (a: i32, b: i32) -> Tuple(i32, i32) ! {
x := try sm_parse(a);
y := try sm_parse(b);
return (x, y);
return .(x, y);
}
// `catch` block that diverges (logs the tag, then returns a fallback)
@@ -178,7 +178,7 @@ sm_or_default :: (n: i32) -> i32 {
// `onfail` + `defer` interleave: cleanup runs only on the error path
// `onfail` + `defer` interleave: cleanup runs only on the error path
sm_acquire :: (fail: bool) -> (i32, !) {
sm_acquire :: (fail: bool) -> i32 ! {
defer print(" smoke defer A\n");
onfail print(" smoke onfail B\n");
if fail { raise error.Acquire; }
@@ -188,7 +188,7 @@ sm_acquire :: (fail: bool) -> (i32, !) {
// `or`-chain: try a, fall to try b; propagate if both fail
// `or`-chain: try a, fall to try b; propagate if both fail
sm_first :: (a: i32, b: i32) -> (i32, !) {
sm_first :: (a: i32, b: i32) -> i32 ! {
v := try sm_parse(a) or try sm_parse(b);
return v;
}

View File

@@ -23,27 +23,27 @@ Provider :: protocol {
// discriminating wrapped/compound RETURNS
getp :: (self: *Self) -> *Box;
geto :: (self: *Self) -> ?Box;
gett :: (self: *Self) -> (Box, Box);
gett :: (self: *Self) -> Tuple(Box, Box);
geta :: (self: *Self) -> [2]Box;
// routing-only wrapped/compound PARAMS
sump :: (self: *Self, p: *Box) -> i32;
sumo :: (self: *Self, o: ?Box) -> i32;
sums :: (self: *Self, s: []Box) -> i32;
suma :: (self: *Self, a: [2]Box) -> i32;
sumt :: (self: *Self, t: (Box, Box)) -> i32;
sumt :: (self: *Self, t: Tuple(Box, Box)) -> i32;
sumn :: (self: *Self, n: *?[]Box) -> i32;
}
impl Provider for Holder {
getp :: (self: *Holder) -> *Box { @self.b }
geto :: (self: *Holder) -> ?Box { self.b }
gett :: (self: *Holder) -> (Box, Box) { (self.b, self.b) }
gett :: (self: *Holder) -> Tuple(Box, Box) { .(self.b, self.b) }
geta :: (self: *Holder) -> [2]Box { r : [2]Box = ---; r[0] = self.b; r[1] = self.b; r }
sump :: (self: *Holder, p: *Box) -> i32 { p.m }
sumo :: (self: *Holder, o: ?Box) -> i32 { o!.m }
sums :: (self: *Holder, s: []Box) -> i32 { s[0].m }
suma :: (self: *Holder, a: [2]Box) -> i32 { a[0].m }
sumt :: (self: *Holder, t: (Box, Box)) -> i32 { t.0.m }
sumt :: (self: *Holder, t: Tuple(Box, Box)) -> i32 { t.0.m }
sumn :: (self: *Holder, n: *?[]Box) -> i32 { if n == null { 0 } else { 6 } }
}
@@ -63,7 +63,7 @@ main :: () -> i32 {
arr : [2]Box = ---; arr[0].m = 2; arr[1].m = 3;
sl : []Box = arr[0..2];
osl : ?[]Box = sl;
tup : (Box, Box) = (one, one);
tup : Tuple(Box, Box) = .(one, one);
sp := p.sump(@one);
so := p.sumo(one);

View File

@@ -29,7 +29,7 @@ WrapU :: union { b: Box; n: i32; }
WrapE :: enum { V: Box; }
main :: () -> i32 {
sz := size_of((Box, i32));
sz := size_of(Tuple(Box, i32));
x : union { b: Box; n: i32 } = ---;
0
}

View File

@@ -23,7 +23,7 @@ main :: () -> i32 {
bp : BoxPtr = @own;
// tuple element own-wins
t : (Box, i32) = ---;
t : Tuple(Box, i32) = ---;
t.0.m = 12;
// enum body-builder child own-wins (payload must be main's `Box`)

View File

@@ -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));

View File

@@ -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
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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");
}
}

View 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
}

View 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
}

View File

@@ -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).

View File

@@ -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
}

View File

@@ -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;
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,6 @@
p 1 2
n 10 20
one 7
swap 4 3
named-type 5 6
eq true

View File

@@ -0,0 +1 @@
0

View File

@@ -0,0 +1,5 @@
t = 1 2
u = 3 4
p = 5 6
q = 7 8
s = 20 10