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 print("{}\n", 1 |> calc(2, 3, 4)); // same = 3 — pipe UFCS
// Tuple return type // 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); s := swap(1, 2);
a := s.0; a := s.0;
b := s.1; b := s.1;
print("{}\n", a); // 2 print("{}\n", a); // 2
print("{}\n", b); // 1 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); t := wrap(99);
print("{}\n", t.0); // 99 print("{}\n", t.0); // 99
} }

View File

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

View File

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

View File

@@ -11,7 +11,7 @@
Pair :: define(declare("Pair"), .tuple(.{ elements = .[ i64, f64 ] })); 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 { main :: () -> i32 {
p : Pair = .{ 3, 2.5 }; p : Pair = .{ 3, 2.5 };

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
#import "modules/std.sx"; #import "modules/std.sx";
main :: () { main :: () {
o : ?(i32,) = null; o : ?Tuple(i32) = null;
x := o ?? 5; // default 'i64' vs payload '(i32,)' -> diagnostic x := o ?? 5; // default 'i64' vs payload '(i32,)' -> diagnostic
print("{}\n", x.0); 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)` 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:55 --> 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 } 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.Bad; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
return n * 10; // success → {n*10, 0} return n * 10; // success → {n*10, 0}

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
E :: error { Bad, Empty } 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.Bad; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
return n * 2; return n * 2;

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
E :: error { BadDigit, Empty, Overflow } 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.BadDigit; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
return n * 2; return n * 2;

View File

@@ -14,7 +14,7 @@
ParseErr :: error { Empty, BadDigit }; 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.Empty; } // pushes a frame
if n < 0 { raise error.BadDigit; } if n < 0 { raise error.BadDigit; }
return n * 2; return n * 2;

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
SmokeErr :: error { Empty, BadDigit, Overflow } SmokeErr :: error { Empty, BadDigit, Overflow }
// value-carrying, named set // 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.BadDigit; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
if n > 99 { raise error.Overflow; } if n > 99 { raise error.Overflow; }
@@ -23,10 +23,10 @@ sm_check :: (ok: bool) -> ! {
} }
// multi-value, inferred set: `try` propagates; SCC absorbs SmokeErr // 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); x := try sm_parse(a);
y := try sm_parse(b); y := try sm_parse(b);
return (x, y); return .(x, y);
} }
// catch with a diverging block body // 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 // 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"); defer print(" defer A\n");
onfail print(" onfail B\n"); onfail print(" onfail B\n");
if fail { raise error.Acquire; } 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 // 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); v := try sm_parse(a) or try sm_parse(b);
return v; return v;
} }
@@ -54,18 +54,18 @@ sm_first :: (a: i32, b: i32) -> (i32, !) {
// --- Composition (ERR E5.1): failable closures, widening, generics --- // --- Composition (ERR E5.1): failable closures, widening, generics ---
// Closure(...) param, try-propagated (the env is carried) // 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); return try cb(n);
} }
// bare fn-type param: a NON-failable closure literal widens into the failable // bare fn-type param: a NON-failable closure literal widens into the failable
// slot (the ∅-widening adapter wraps `{value, 0}`) // 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; return cb(n) catch (e) -1;
} }
// generic ($T) value-carrying failable composition, monomorphized per call // 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(); return try f();
} }
@@ -105,9 +105,9 @@ main :: () {
if !gerr { print("or-chain: {}\n", g); } if !gerr { print("or-chain: {}\n", g); }
// multi-value failable consumed by catch (tuple body) // 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); 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); print("pair-ok: {} {}\n", p2, q2);
// pure failable: absorb with no-binding catch // pure failable: absorb with no-binding catch
@@ -120,15 +120,15 @@ main :: () {
iv, ierr := sm_acquire(false); iv, ierr := sm_acquire(false);
// composition: inline failable closure literal through a Closure(...) param // 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: {}\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 // non-failable closure literal widened into the failable bare slot
print("widen: {}\n", sm_widen(closure((x: i32) -> i32 => x + 1), 9)); // 10 print("widen: {}\n", sm_widen(closure((x: i32) -> i32 => x + 1), 9)); // 10
// generic failable composition (monomorphized at i32) // 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"); print("errors ok\n");
} }

View File

@@ -7,7 +7,7 @@
E :: error { Bad, Empty } 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.Bad; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
return n * 2; return n * 2;

View File

@@ -7,7 +7,7 @@
E :: error { Bad, Empty } 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.Bad; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
return n * 2; return n * 2;

View File

@@ -10,12 +10,12 @@
E :: error { Neg } 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 { main :: () -> i32 {
// block-body and arrow-body failable closures, called directly // block-body and arrow-body failable closures, called directly
m := closure((x: i64) -> (i64, !E) { if x < 0 { raise error.Neg; } return x * 2; }); m := closure((x: i64) -> i64 !E { if x < 0 { raise error.Neg; } return x * 2; });
n := closure((x: i64) -> (i64, !E) => x + 1); 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 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 // failable closure passed as a Closure(...) parameter

View File

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

View File

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

View File

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

View File

@@ -9,7 +9,7 @@
E :: error { Neg } 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 { main :: () -> i32 {
// `-> i32` (non-failable) but the body raises → lambda-specific hint: // `-> i32` (non-failable) but the body raises → lambda-specific hint:

View File

@@ -9,21 +9,21 @@
E :: error { Bad } 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 { main :: () -> i32 {
// success, consumed by catch // 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 // success, consumed by destructure (binds value + error slot); the value
// slot is read only under an `if !err` guard (ERR E1.8 path-sensitivity) // 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 if !err { print("destr={} ok=true\n", r); } // destr=9 ok=true
// failure path: the raised tag propagates through the generic `try` // 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 // 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; return 0;
} }

View File

@@ -13,7 +13,7 @@
E :: error { Z } E :: error { Z }
bare :: (cb: (i64) -> i64, n: i64) -> i64 { return cb(n); } 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 { main :: () -> i32 {
inc := closure((x: i64) -> i64 => x + 1); // capture-free closure var inc := closure((x: i64) -> i64 => x + 1); // capture-free closure var

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,7 @@
E :: error { Bad, Empty } 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.Bad; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
return n * 2; return n * 2;

View File

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

View File

@@ -22,13 +22,13 @@ Color :: enum { red; green; blue; }
E :: error { Nope } E :: error { Nope }
// F1: bare-value success path AND explicit-tuple error path in one function. // 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} 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. // 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 == "red" { return .red; } // bare value, inline path → {0, 0}
if s == "blue" { return .blue; } // bare value, inline path → {2, 0} if s == "blue" { return .blue; } // bare value, inline path → {2, 0}
raise error.Nope; // inline error path → {undef, 1} raise error.Nope; // inline error path → {undef, 1}

View File

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

View File

@@ -5,4 +5,4 @@ LE :: error { Bad }
Box :: struct ($R: Type) { v: R; } Box :: struct ($R: Type) { v: R; }
// Returns `($R, !LE)` — a value-failable. `$R` is inferred from the arg. // 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; } impl Box(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
snapshot :: (..xs: Box) -> void { 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); 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 // Tuple type `(..xs.T)` — heterogeneous (i64, string), matched by the
// value-projection `(..xs.get)`. // value-projection `(..xs.get)`.
snap :: (..xs: Box) -> void { snap :: (..xs: Box) -> void {
t : (..xs.T) = (..xs.get); t : Tuple(..xs.T) = .(..xs.get);
print("0={} 1={}\n", t.0, t.1); print("0={} 1={}\n", t.0, t.1);
} }

View File

@@ -7,19 +7,19 @@
Box :: struct($R: Type, ..$Ts: []Type) { Box :: struct($R: Type, ..$Ts: []Type) {
r: $R; r: $R;
pair: (..$Ts); // tuple of the pack's element types pair: Tuple(..$Ts); // tuple of the pack's element types
} }
main :: () -> i32 { main :: () -> i32 {
// Box(i64, i32, string): R=i64, Ts=[i32, string], pair: (i32, string). // Box(i64, i32, string): R=i64, Ts=[i32, string], pair: (i32, string).
a : Box(i64, i32, string) = ---; a : Box(i64, i32, string) = ---;
a.r = 7; 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); print("a: r={} 0={} 1={}\n", a.r, a.pair.0, a.pair.1);
// A different shape → a different per-position tuple field. // A different shape → a different per-position tuple field.
b : Box(bool, string, bool) = ---; // Ts=[string, bool], pair: (string, bool) 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); print("b: 0={} 1={}\n", b.pair.0, b.pair.1);
0 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; } impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
Combined :: struct($R: Type, ..$Ts: []Type) { 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; value: $R;
} }
@@ -22,7 +22,7 @@ main :: () -> i32 {
// Combined(i64, i64, string): R=i64, Ts=[i64, string], // Combined(i64, i64, string): R=i64, Ts=[i64, string],
// sources: (VL(i64), VL(string)). // sources: (VL(i64), VL(string)).
c : Combined(i64, i64, 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; c.value = 99;
print("{} {} {}\n", c.sources.0.get(), c.sources.1.get(), c.value); // 10 hi 99 print("{} {} {}\n", c.sources.0.get(), c.sources.1.get(), c.value); // 10 hi 99
0 0

View File

@@ -11,7 +11,7 @@ IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Combined :: struct($R: Type, ..$Ts: []Type) { Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts)); sources: Tuple(..VL(Ts));
value: $R; 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; } impl VL(string) for StrCell { get :: (self: *StrCell) -> string => self.s; }
Combined :: struct($R: Type, ..$Ts: []Type) { Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts)); sources: Tuple(..VL(Ts));
value: $R; value: $R;
} }
build :: (..sources: VL) -> void { build :: (..sources: VL) -> void {
c : Combined(i64, ..sources.T) = ---; 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()); 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; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Combined :: struct($R: Type, ..$Ts: []Type) { Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts)); sources: Tuple(..VL(Ts));
value: $R; value: $R;
} }
impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; } impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; }
map :: (mapper: Closure(..sources.T) -> $R, ..sources: VL) -> VL($R) { map :: (mapper: Closure(..sources.T) -> $R, ..sources: VL) -> VL($R) {
c : Combined($R, ..sources.T) = ---; c : Combined($R, ..sources.T) = ---;
c.sources = (..sources); c.sources = .(..sources);
c.value = mapper(..sources.get); c.value = mapper(..sources.get);
return xx c; return xx c;
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ IntCell :: struct { v: i64; }
impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; } impl VL(i64) for IntCell { get :: (self: *IntCell) -> i64 => self.v; }
Combined :: struct($R: Type, ..$Ts: []Type) { Combined :: struct($R: Type, ..$Ts: []Type) {
sources: (..VL(Ts)); sources: Tuple(..VL(Ts));
value: $R; value: $R;
} }
impl VL($R) for Combined($R, ..$Ts) { get :: (self: *Combined) -> $R => self.value; } 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) { make :: (..sources: VL) -> VL(i64) {
c : Combined(i64, ..sources.T) = ---; c : Combined(i64, ..sources.T) = ---;
c.value = 99; c.value = 99;
c.sources = (..sources); c.sources = .(..sources);
return xx c; // Combined__i64_i64 -> VL(i64) 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
// 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.BadDigit; }
if n == 0 { raise error.Empty; } if n == 0 { raise error.Empty; }
if n > 99 { raise error.Overflow; } 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
// 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); x := try sm_parse(a);
y := try sm_parse(b); y := try sm_parse(b);
return (x, y); return .(x, y);
} }
// `catch` block that diverges (logs the tag, then returns a fallback) // `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
// `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"); defer print(" smoke defer A\n");
onfail print(" smoke onfail B\n"); onfail print(" smoke onfail B\n");
if fail { raise error.Acquire; } 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
// `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); v := try sm_parse(a) or try sm_parse(b);
return v; return v;
} }

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ main :: () -> i32 {
bp : BoxPtr = @own; bp : BoxPtr = @own;
// tuple element own-wins // tuple element own-wins
t : (Box, i32) = ---; t : Tuple(Box, i32) = ---;
t.0.m = 12; t.0.m = 12;
// enum body-builder child own-wins (payload must be main's `Box`) // 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)); print("size_of((i32)->i32) = {}\n", size_of((i32) -> i32));
// Tuple literal reinterpreted as tuple type at the type-demanding site. // 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. // Aliases.
print("size_of(Ptr) = {}\n", size_of(Ptr)); print("size_of(Ptr) = {}\n", size_of(Ptr));

View File

@@ -6,18 +6,18 @@
#import "modules/std.sx"; #import "modules/std.sx";
Box :: struct { xs: (i32, i32); } Box :: struct { xs: Tuple(i32, i32); }
swap :: (a: i64, b: i64) -> (i64, i64) { (b, a) } swap :: (a: i64, b: i64) -> Tuple(i64, i64) { .(b, a) }
fst :: (t: (i64, i64)) -> i64 { t.0 } fst :: (t: Tuple(i64, i64)) -> i64 { t.0 }
main :: () -> i32 { main :: () -> i32 {
// Inferred positional tuple + numeric field access. // Inferred positional tuple + numeric field access.
pair := (40, 2); pair := .(40, 2);
print("pair {} {}\n", pair.0, pair.1); print("pair {} {}\n", pair.0, pair.1);
// Named tuple: named + numeric access. // Named tuple: named + numeric access.
named := (x: 10, y: 20); named := .(x = 10, y = 20);
print("named {} {} {}\n", named.x, named.0, named.1); print("named {} {} {}\n", named.x, named.0, named.1);
// Element into a typed local (access path, not just print). // 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. // Tuple-typed struct field: store a tuple value, read both elements.
box : Box = ---; box : Box = ---;
box.xs = (7, 9); box.xs = .(7, 9);
print("field {} {}\n", box.xs.0, box.xs.1); print("field {} {}\n", box.xs.0, box.xs.1);
// Return a tuple from a function. // Return a tuple from a function.
@@ -35,21 +35,21 @@ main :: () -> i32 {
print("ret {} {}\n", s.0, s.1); print("ret {} {}\n", s.0, s.1);
// Pass a tuple by value. // Pass a tuple by value.
print("pass {}\n", fst((11, 22))); print("pass {}\n", fst(.(11, 22)));
// Operators: equality, concatenation, repetition, membership, lex. // Operators: equality, concatenation, repetition, membership, lex.
print("eq {}\n", (1, 2) == (1, 2)); print("eq {}\n", .(1, 2) == .(1, 2));
c := (1, 2) + (3, 4); c := .(1, 2) + .(3, 4);
print("concat {} {}\n", c.0, c.3); print("concat {} {}\n", c.0, c.3);
r := (1, 2) * 3; r := .(1, 2) * 3;
print("rep {} {}\n", r.0, r.5); print("rep {} {}\n", r.0, r.5);
print("mem {}\n", 3 in (1, 2, 3)); print("mem {}\n", 3 in .(1, 2, 3));
print("lex {}\n", (1, 2) < (1, 3)); print("lex {}\n", .(1, 2) < .(1, 3));
// Mixed-size fields: a tuple with both an i64 and a string (16-byte fat // 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 // pointer). Field types are tracked per-position, so reading each back is
// typed correctly (i64 prints as a number, string as text). // typed correctly (i64 prints as a number, string as text).
mixed := (42, "hi"); mixed := .(42, "hi");
print("mixed {} {}\n", mixed.0, mixed.1); print("mixed {} {}\n", mixed.0, mixed.1);
0 0
} }

View File

@@ -9,13 +9,13 @@
main :: () -> i32 { main :: () -> i32 {
// Positional element assignment. // Positional element assignment.
a : (i32, string) = ---; a : Tuple(i32, string) = ---;
a.0 = 11; a.0 = 11;
a.1 = "x"; a.1 = "x";
print("a: {} {}\n", a.0, a.1); print("a: {} {}\n", a.0, a.1);
// Named tuple: write + read by name, and read by position. // 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.x = 22;
p.y = "y"; p.y = "y";
print("p: x={} y={} .0={}\n", p.x, p.y, p.0); print("p: x={} y={} .0={}\n", p.x, p.y, p.0);

View File

@@ -81,26 +81,26 @@ main :: () {
// Basic tuple destructuring // Basic tuple destructuring
{ {
da, db := (10, 20); da, db := .(10, 20);
print("basic: {} {}\n", da, db); print("basic: {} {}\n", da, db);
} }
// Destructure from function return // 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); dx, dy := dswap(1, 2);
print("fn: {} {}\n", dx, dy); print("fn: {} {}\n", dx, dy);
} }
// Discard with _ // Discard with _
{ {
_, dsecond := (100, 200); _, dsecond := .(100, 200);
print("discard: {}\n", dsecond); print("discard: {}\n", dsecond);
} }
// Three elements // Three elements
{ {
da3, db3, dc3 := (1, 2, 3); da3, db3, dc3 := .(1, 2, 3);
print("triple: {} {} {}\n", da3, db3, dc3); print("triple: {} {} {}\n", da3, db3, dc3);
} }
} }

View File

@@ -9,18 +9,18 @@ main :: () {
// --- Tuples --- // --- Tuples ---
{ {
print("=== Tuples ===\n"); print("=== Tuples ===\n");
pair := (40, 2); pair := .(40, 2);
print("{}\n", pair.0); print("{}\n", pair.0);
print("{}\n", pair.1); print("{}\n", pair.1);
named := (x: 10, y: 20); named := .(x = 10, y = 20);
print("{}\n", named.x); print("{}\n", named.x);
print("{}\n", named.0); print("{}\n", named.0);
single := (42,); single := .(42);
print("{}\n", single.0); print("{}\n", single.0);
zeroed : (i32, i32) = ---; zeroed : Tuple(i32, i32) = ---;
print("{}\n", zeroed.0); print("{}\n", zeroed.0);
print("{}\n", zeroed.1); print("{}\n", zeroed.1);
} }

View File

@@ -44,20 +44,20 @@ main :: () {
print("=== Tuple Operators ===\n"); print("=== Tuple Operators ===\n");
// Equality // Equality
print("{}\n", (1, 2) == (1, 2)); // true print("{}\n", .(1, 2) == .(1, 2)); // true
print("{}\n", (1, 2) == (1, 3)); // false print("{}\n", .(1, 2) == .(1, 3)); // false
print("{}\n", (1, 2) != (1, 3)); // true print("{}\n", .(1, 2) != .(1, 3)); // true
print("{}\n", (1, 2) != (1, 2)); // false print("{}\n", .(1, 2) != .(1, 2)); // false
// Concatenation // Concatenation
c := (1, 2) + (3, 4); c := .(1, 2) + .(3, 4);
print("{}\n", c.0); // 1 print("{}\n", c.0); // 1
print("{}\n", c.1); // 2 print("{}\n", c.1); // 2
print("{}\n", c.2); // 3 print("{}\n", c.2); // 3
print("{}\n", c.3); // 4 print("{}\n", c.3); // 4
// Repetition // Repetition
r := (1, 2) * 3; r := .(1, 2) * 3;
print("{}\n", r.0); // 1 print("{}\n", r.0); // 1
print("{}\n", r.1); // 2 print("{}\n", r.1); // 2
print("{}\n", r.2); // 1 print("{}\n", r.2); // 1
@@ -66,16 +66,16 @@ main :: () {
print("{}\n", r.5); // 2 print("{}\n", r.5); // 2
// Lexicographic comparison // Lexicographic comparison
print("{}\n", (1, 2) < (1, 3)); // true print("{}\n", .(1, 2) < .(1, 3)); // true
print("{}\n", (1, 3) < (1, 2)); // false print("{}\n", .(1, 3) < .(1, 2)); // false
print("{}\n", (1, 2) < (1, 2)); // false print("{}\n", .(1, 2) < .(1, 2)); // false
print("{}\n", (1, 2) <= (1, 2)); // true print("{}\n", .(1, 2) <= .(1, 2)); // true
print("{}\n", (2, 0) > (1, 9)); // true print("{}\n", .(2, 0) > .(1, 9)); // true
print("{}\n", (1, 2) >= (1, 2)); // true print("{}\n", .(1, 2) >= .(1, 2)); // true
// Membership // Membership
print("{}\n", 2 in (1, 2, 3)); // true print("{}\n", 2 in .(1, 2, 3)); // true
print("{}\n", 5 in (1, 2, 3)); // false print("{}\n", 5 in .(1, 2, 3)); // false
} }
// --- Directory imports --- // --- Directory imports ---
@@ -189,15 +189,15 @@ main :: () {
} }
{ {
if 1 == (1,) { if 1 == .(1) {
print("1 == (1)\n"); print("1 == (1)\n");
} }
if (1,) == (1) { if .(1) == (1) {
print("(1) == 1\n"); print("(1) == 1\n");
} }
if (1,) == 1 { if .(1) == 1 {
print("1 == 1\n"); 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). // Regression (issue 0089 — attempt-2 completeness across binding forms).
#import "modules/std.sx"; #import "modules/std.sx";
pair :: () -> (i64, i64) { (1, 2) } pair :: () -> Tuple(i64, i64) { .(1, 2) }
maybe :: () -> ?i64 { return 42; } maybe :: () -> ?i64 { return 42; }
// Function named with a reserved spelling — bare-callable (no backtick at call). // Function named with a reserved spelling — bare-callable (no backtick at call).

View File

@@ -27,7 +27,7 @@ big_host :: () -> i32 {
} }
d_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))); print("a: {} b: {}\n", type_name(type_of(a)), type_name(type_of(b)));
0 0
} }

View File

@@ -21,7 +21,7 @@ main :: () -> i32 {
print("tag={}\n", b.tag); print("tag={}\n", b.tag);
// A tuple with a void element. // A tuple with a void element.
t : (void, i32) = .{ {}, 9 }; t : Tuple(void, i32) = .{ {}, 9 };
print("t1={}\n", t.1); print("t1={}\n", t.1);
return 0; return 0;
} }

View File

@@ -11,24 +11,24 @@
main :: () { main :: () {
// Optional + float fields. // 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 print("{} {}\n", t.0 ?? -1, t.1); // 7 3.000000
// int -> float coercion on a tuple element. // 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 print("{} {}\n", u.0, u.1); // 3.000000 4
// Named tuple. // 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 print("{} {}\n", n.x ?? -1, n.y); // 5 2.500000
// Variable elements flowing into an optional tuple field. // Variable elements flowing into an optional tuple field.
a := 9; a := 9;
b := 1.5; b := 1.5;
v : (?i64, f64) = .{ a, b }; v : Tuple(?i64, f64) = .{ a, b };
print("{} {}\n", v.0 ?? -1, v.1); // 9 1.500000 print("{} {}\n", v.0 ?? -1, v.1); // 9 1.500000
// A bare `null` element into an optional tuple field. // 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 print("{} {}\n", w.0 ?? -1, w.1); // -1 8
} }

View File

@@ -29,10 +29,10 @@ main :: () {
print("{}\n", fns[0](3, 4)); // 7 print("{}\n", fns[0](3, 4)); // 7
// A 1-tuple type still requires the trailing comma. // A 1-tuple type still requires the trailing comma.
one : (i64,) = (9,); one : Tuple(i64) = .(9);
print("{}\n", one.0); // 9 print("{}\n", one.0); // 9
// A 2-tuple is unaffected. // A 2-tuple is unaffected.
two : (i64, i64) = (40, 2); two : Tuple(i64, i64) = .(40, 2);
print("{}\n", two.0 + two.1); // 42 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

View File

@@ -263,7 +263,7 @@ is_long_flag :: (s: string) -> bool {
// Parse `args` (the logical argv) against the `commands` table, writing // Parse `args` (the logical argv) against the `commands` table, writing
// the offending token into `diag` on the error path. See the section // the offending token into `diag` on the error path. See the section
// header for grammar, failure contract, and heap discipline. // header for grammar, failure contract, and heap discipline.
parse :: (args: []string, commands: []Command, diag: *Diag) -> (Parsed, !CliError) { parse :: (args: []string, commands: []Command, diag: *Diag) -> Parsed !CliError {
// ── Dispatch: match (args[0], args[1]) against the command table ── // ── Dispatch: match (args[0], args[1]) against the command table ──
if args.len < 2 { if args.len < 2 {
diag.index = if args.len == 0 then -1 else 0; diag.index = if args.len == 0 then -1 else 0;

View File

@@ -52,7 +52,7 @@ Event :: struct {
Loop :: struct { Loop :: struct {
kq: i32 = -1; kq: i32 = -1;
init :: () -> (Loop, !EventErr) { init :: () -> Loop !EventErr {
q := kqb.kqueue(); q := kqb.kqueue();
if q < 0 { raise error.Init; } if q < 0 { raise error.Init; }
return Loop.{ kq = q }; return Loop.{ kq = q };
@@ -96,7 +96,7 @@ Loop :: struct {
// Fill `out` with ready events, waiting at most `timeout_ms` // Fill `out` with ready events, waiting at most `timeout_ms`
// (negative = forever). Returns the count; 0 is a timeout. // (negative = forever). Returns the count; 0 is a timeout.
wait :: (self: *Loop, out: []Event, timeout_ms: i64) -> (i64, !EventErr) { wait :: (self: *Loop, out: []Event, timeout_ms: i64) -> i64 !EventErr {
raw : [64]kqb.Kevent = ---; raw : [64]kqb.Kevent = ---;
cap : i64 = 64; cap : i64 = 64;
if xx out.len < cap { cap = xx out.len; } if xx out.len < cap { cap = xx out.len; }

View File

@@ -263,7 +263,7 @@ Server :: struct {
ctx: usize = 0; ctx: usize = 0;
ps: *PoolState = null; // non-null iff cfg.thread_pool_count > 0 ps: *PoolState = null; // non-null iff cfg.thread_pool_count > 0
init :: (cfg: Config, handler: (*Request, *Response, usize) -> void, ctx: usize) -> (Server, !HttpErr) { init :: (cfg: Config, handler: (*Request, *Response, usize) -> void, ctx: usize) -> Server !HttpErr {
lfd := socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0); lfd := socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0);
if lfd < 0 { raise error.Bind; } if lfd < 0 { raise error.Bind; }
one : i32 = 1; one : i32 = 1;

View File

@@ -113,7 +113,7 @@ async :: ufcs (io: Io, worker: Closure(..$args) -> $R, ..$args) -> Future($R) {
// `await(f)` — value-carrying failable. `.ready` → the result; `.failed` // `await(f)` — value-carrying failable. `.ready` → the result; `.failed`
// / `.canceled` → raise the stored / cancellation error. // / `.canceled` → raise the stored / cancellation error.
await :: ufcs (f: *Future($R)) -> ($R, !IoErr) { await :: ufcs (f: *Future($R)) -> $R !IoErr {
if f.canceled.load(.acquire) { raise error.Canceled; } if f.canceled.load(.acquire) { raise error.Canceled; }
if f.state == .canceled { raise error.Canceled; } if f.state == .canceled { raise error.Canceled; }
if f.state == .failed { raise error.Failed; } if f.state == .failed { raise error.Failed; }

View File

@@ -321,7 +321,7 @@ write_object :: (obj: Object, sink: *Sink) -> !JsonError {
// bytes written. Raises `error.Overflow` if `dst` is too small (the // bytes written. Raises `error.Overflow` if `dst` is too small (the
// partial contents of `dst` are then undefined — nothing is truncated // partial contents of `dst` are then undefined — nothing is truncated
// silently). No allocation. // silently). No allocation.
write_to_buffer :: (v: Value, dst: []u8) -> (i64, !JsonError) { write_to_buffer :: (v: Value, dst: []u8) -> i64 !JsonError {
sink := Sink.{ dst = dst }; sink := Sink.{ dst = dst };
try write_value(v, @sink); try write_value(v, @sink);
return sink.pos; return sink.pos;
@@ -386,7 +386,7 @@ JsonParseError :: error { UnexpectedToken, UnexpectedEnd, BadEscape, BadNumber,
// Lowercase/uppercase hex nibble value (0..15) of an ASCII byte; a non-hex // Lowercase/uppercase hex nibble value (0..15) of an ASCII byte; a non-hex
// byte in a `\uXXXX` escape is a `BadEscape`. // byte in a `\uXXXX` escape is a `BadEscape`.
hex_value :: (c: u8) -> (i64, !JsonParseError) { hex_value :: (c: u8) -> i64 !JsonParseError {
if c >= 48 and c <= 57 { return (cast(i64) c) - 48; } // '0'..'9' if c >= 48 and c <= 57 { return (cast(i64) c) - 48; } // '0'..'9'
if c >= 97 and c <= 102 { return (cast(i64) c) - 97 + 10; } // 'a'..'f' if c >= 97 and c <= 102 { return (cast(i64) c) - 97 + 10; } // 'a'..'f'
if c >= 65 and c <= 70 { return (cast(i64) c) - 65 + 10; } // 'A'..'F' if c >= 65 and c <= 70 { return (cast(i64) c) - 65 + 10; } // 'A'..'F'
@@ -450,7 +450,7 @@ Parser :: struct {
// Read 4 hex digits at `i` (which must lie within [.., end)); returns // Read 4 hex digits at `i` (which must lie within [.., end)); returns
// the 16-bit value. Fewer than 4 digits before `end` is a BadEscape. // the 16-bit value. Fewer than 4 digits before `end` is a BadEscape.
read_hex4 :: (self: *Parser, i: i64, end: i64) -> (i64, !JsonParseError) { read_hex4 :: (self: *Parser, i: i64, end: i64) -> i64 !JsonParseError {
if i + 4 > end { raise error.BadEscape; } if i + 4 > end { raise error.BadEscape; }
v := 0; v := 0;
k := 0; k := 0;
@@ -464,7 +464,7 @@ Parser :: struct {
// Decode the escaped string body in [start, end) into `out`, returning // Decode the escaped string body in [start, end) into `out`, returning
// the decoded byte length. Pass 1 (in parse_string) guarantees there is // the decoded byte length. Pass 1 (in parse_string) guarantees there is
// no dangling backslash, so the byte after every `\` is in range. // no dangling backslash, so the byte after every `\` is in range.
decode_into :: (self: *Parser, start: i64, end: i64, out: [*]u8) -> (i64, !JsonParseError) { decode_into :: (self: *Parser, start: i64, end: i64, out: [*]u8) -> i64 !JsonParseError {
di := 0; di := 0;
i := start; i := start;
while i < end { while i < end {
@@ -511,7 +511,7 @@ Parser :: struct {
// a zero-copy VIEW into `src` when the body has no escapes; otherwise // a zero-copy VIEW into `src` when the body has no escapes; otherwise
// decodes into an `alloc`-ed buffer (bounded by the raw span). `pos` // decodes into an `alloc`-ed buffer (bounded by the raw span). `pos`
// ends just past the closing quote. // ends just past the closing quote.
parse_string :: (self: *Parser) -> (string, !JsonParseError) { parse_string :: (self: *Parser) -> string !JsonParseError {
self.pos += 1; // consume opening quote self.pos += 1; // consume opening quote
start := self.pos; start := self.pos;
has_escape := false; has_escape := false;
@@ -547,7 +547,7 @@ Parser :: struct {
// Parse an i64 integer (optional '-', then digits). Rejects leading // Parse an i64 integer (optional '-', then digits). Rejects leading
// zeros, a fraction/exponent tail, and any value outside i64 — all // zeros, a fraction/exponent tail, and any value outside i64 — all
// `BadNumber`. Accumulates in NEGATIVE space so i64 MIN parses exactly. // `BadNumber`. Accumulates in NEGATIVE space so i64 MIN parses exactly.
parse_number :: (self: *Parser) -> (i64, !JsonParseError) { parse_number :: (self: *Parser) -> i64 !JsonParseError {
// i64 bounds, built positionally because |MIN| is not a // i64 bounds, built positionally because |MIN| is not a
// representable positive i64 literal. `min_div10` is `MIN / 10` // representable positive i64 literal. `min_div10` is `MIN / 10`
// truncated toward zero (remainder -8) — the digit loop's overflow // truncated toward zero (remainder -8) — the digit loop's overflow
@@ -585,7 +585,7 @@ Parser :: struct {
} }
// Parse an array starting at '['. Builds an `Array` through `alloc`. // Parse an array starting at '['. Builds an `Array` through `alloc`.
parse_array :: (self: *Parser) -> (Value, !JsonParseError) { parse_array :: (self: *Parser) -> Value !JsonParseError {
self.pos += 1; // consume '[' self.pos += 1; // consume '['
arr : Array = .{}; arr : Array = .{};
self.skip_ws(); self.skip_ws();
@@ -609,7 +609,7 @@ Parser :: struct {
// Parse an object starting at '{'. Keys must be strings; insertion // Parse an object starting at '{'. Keys must be strings; insertion
// order is preserved (duplicate keys are kept, never merged). // order is preserved (duplicate keys are kept, never merged).
parse_object :: (self: *Parser) -> (Value, !JsonParseError) { parse_object :: (self: *Parser) -> Value !JsonParseError {
self.pos += 1; // consume '{' self.pos += 1; // consume '{'
obj : Object = .{}; obj : Object = .{};
self.skip_ws(); self.skip_ws();
@@ -640,7 +640,7 @@ Parser :: struct {
} }
// Parse any single value (after skipping leading whitespace). // Parse any single value (after skipping leading whitespace).
parse_value :: (self: *Parser) -> (Value, !JsonParseError) { parse_value :: (self: *Parser) -> Value !JsonParseError {
self.skip_ws(); self.skip_ws();
if self.pos >= self.src.len { raise error.UnexpectedEnd; } if self.pos >= self.src.len { raise error.UnexpectedEnd; }
c := self.src[self.pos]; c := self.src[self.pos];
@@ -659,7 +659,7 @@ Parser :: struct {
// `alloc` for composite nodes and decoded (escaped) strings. Un-escaped // `alloc` for composite nodes and decoded (escaped) strings. Un-escaped
// string values are VIEWS into `src` and are valid only while `src` lives. // string values are VIEWS into `src` and are valid only while `src` lives.
// Trailing non-whitespace after the value raises `error.TrailingGarbage`. // Trailing non-whitespace after the value raises `error.TrailingGarbage`.
parse :: (src: string, alloc: Allocator) -> (Value, !JsonParseError) { parse :: (src: string, alloc: Allocator) -> Value !JsonParseError {
p := Parser.{ src = src, alloc = alloc }; p := Parser.{ src = src, alloc = alloc };
v := try p.parse_value(); v := try p.parse_value();
p.skip_ws(); p.skip_ws();

View File

@@ -804,7 +804,7 @@ go :: ufcs (self: *Scheduler, work: Closure() -> $R) -> *Task($R) {
// Suspend the caller until the task completes; return its value (or raise on // Suspend the caller until the task completes; return its value (or raise on
// cancel). MUST be called from inside a fiber (so there is a `self.current` to // cancel). MUST be called from inside a fiber (so there is a `self.current` to
// park) — typically from a fiber spawned via `s.spawn(...)`. // park) — typically from a fiber spawned via `s.spawn(...)`.
wait :: ufcs (t: *Task($R)) -> ($R, !TaskErr) { wait :: ufcs (t: *Task($R)) -> $R !TaskErr {
if t.canceled != 0 { raise error.Canceled; } if t.canceled != 0 { raise error.Canceled; }
if t.state == .pending { if t.state == .pending {
// ONE waiter per task (enforced). A `Task` holds a single `waiter` slot; // ONE waiter per task (enforced). A `Task` holds a single `waiter` slot;

View File

@@ -93,7 +93,7 @@ SockErr :: error {
// Accept one pending connection on a nonblocking listener. A connection // Accept one pending connection on a nonblocking listener. A connection
// that died between queueing and accept (ECONNABORTED) is skipped, not // that died between queueing and accept (ECONNABORTED) is skipped, not
// surfaced — the listener is fine. // surfaced — the listener is fine.
accept_nb :: (fd: i32) -> (i32, !SockErr) { accept_nb :: (fd: i32) -> i32 !SockErr {
while true { while true {
c := accept(fd, null, null); c := accept(fd, null, null);
if c >= 0 { return c; } if c >= 0 { return c; }
@@ -107,7 +107,7 @@ accept_nb :: (fd: i32) -> (i32, !SockErr) {
// Read up to `cap` bytes. Returns the byte count (> 0); an orderly EOF // Read up to `cap` bytes. Returns the byte count (> 0); an orderly EOF
// or a peer reset is Closed. // or a peer reset is Closed.
read_nb :: (fd: i32, buf: [*]u8, cap: usize) -> (i64, !SockErr) { read_nb :: (fd: i32, buf: [*]u8, cap: usize) -> i64 !SockErr {
while true { while true {
n := read(fd, buf, cap); n := read(fd, buf, cap);
if n > 0 { return xx n; } if n > 0 { return xx n; }
@@ -123,7 +123,7 @@ read_nb :: (fd: i32, buf: [*]u8, cap: usize) -> (i64, !SockErr) {
// Write up to `len` bytes, returning how many the kernel took (possibly // Write up to `len` bytes, returning how many the kernel took (possibly
// fewer — the caller continues from there on the next writability). // fewer — the caller continues from there on the next writability).
write_nb :: (fd: i32, buf: [*]u8, len: usize) -> (i64, !SockErr) { write_nb :: (fd: i32, buf: [*]u8, len: usize) -> i64 !SockErr {
while true { while true {
n := write(fd, buf, len); n := write(fd, buf, len);
if n >= 0 { return xx n; } if n >= 0 { return xx n; }

View File

@@ -106,7 +106,7 @@ Thread :: struct {
// `entry` is the C->sx boundary: abi(.c), fabricates its own // `entry` is the C->sx boundary: abi(.c), fabricates its own
// Context before touching default-conv sx code (examples/1636). // Context before touching default-conv sx code (examples/1636).
spawn :: (entry: (*void) -> *void abi(.c), arg: *void) -> (Thread, !ThreadErr) { spawn :: (entry: (*void) -> *void abi(.c), arg: *void) -> Thread !ThreadErr {
t : Thread = .{}; t : Thread = .{};
if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; } if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; }
return t; return t;
@@ -144,7 +144,7 @@ Pool :: struct {
// Heap-allocate (the pool must never move: workers hold its address, // Heap-allocate (the pool must never move: workers hold its address,
// and it embeds a live mutex), init in place, spawn the workers. // and it embeds a live mutex), init in place, spawn the workers.
create :: (workers: i64, backlog: i64) -> (*Pool, !ThreadErr) { create :: (workers: i64, backlog: i64) -> *Pool !ThreadErr {
alloc := context.allocator; alloc := context.allocator;
p : *Pool = xx alloc.alloc_bytes(size_of(Pool)); p : *Pool = xx alloc.alloc_bytes(size_of(Pool));
p.* = Pool.{}; p.* = Pool.{};

0
library/modules/ui/animation.sx Executable file → Normal file
View File

0
library/modules/ui/button.sx Executable file → Normal file
View File

0
library/modules/ui/dock.sx Executable file → Normal file
View File

0
library/modules/ui/events.sx Executable file → Normal file
View File

0
library/modules/ui/font.sx Executable file → Normal file
View File

0
library/modules/ui/gesture.sx Executable file → Normal file
View File

0
library/modules/ui/glyph_cache.sx Executable file → Normal file
View File

0
library/modules/ui/image.sx Executable file → Normal file
View File

0
library/modules/ui/label.sx Executable file → Normal file
View File

0
library/modules/ui/layout.sx Executable file → Normal file
View File

Some files were not shown because too many files have changed in this diff Show More