diff --git a/examples/basic/0038-basic-dead-code-after-terminator.sx b/examples/basic/0038-basic-dead-code-after-terminator.sx index d7ae375e..bc630e9d 100644 --- a/examples/basic/0038-basic-dead-code-after-terminator.sx +++ b/examples/basic/0038-basic-dead-code-after-terminator.sx @@ -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; } diff --git a/examples/diagnostics/1157-diagnostics-catch-binding-needs-parens.sx b/examples/diagnostics/1157-diagnostics-catch-binding-needs-parens.sx index 4963efb1..4eb3ee02 100644 --- a/examples/diagnostics/1157-diagnostics-catch-binding-needs-parens.sx +++ b/examples/diagnostics/1157-diagnostics-catch-binding-needs-parens.sx @@ -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 }; diff --git a/examples/errors/1011-errors-value-failable.sx b/examples/errors/1011-errors-value-failable.sx index 8fbe3c2f..e24f8d5e 100644 --- a/examples/errors/1011-errors-value-failable.sx +++ b/examples/errors/1011-errors-value-failable.sx @@ -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} diff --git a/examples/errors/1012-errors-value-failable-consume.sx b/examples/errors/1012-errors-value-failable-consume.sx index 8ef34fb6..be81a50e 100644 --- a/examples/errors/1012-errors-value-failable-consume.sx +++ b/examples/errors/1012-errors-value-failable-consume.sx @@ -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; } diff --git a/examples/errors/1013-errors-value-failable-reject.sx b/examples/errors/1013-errors-value-failable-reject.sx index 98f80e14..68145f72 100644 --- a/examples/errors/1013-errors-value-failable-reject.sx +++ b/examples/errors/1013-errors-value-failable-reject.sx @@ -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; } diff --git a/examples/errors/1014-errors-failable-or.sx b/examples/errors/1014-errors-failable-or.sx index 6860668f..6ca41625 100644 --- a/examples/errors/1014-errors-failable-or.sx +++ b/examples/errors/1014-errors-failable-or.sx @@ -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; diff --git a/examples/errors/1018-errors-multi-value-failable.sx b/examples/errors/1018-errors-multi-value-failable.sx index 4d486f1c..9301a21f 100644 --- a/examples/errors/1018-errors-multi-value-failable.sx +++ b/examples/errors/1018-errors-multi-value-failable.sx @@ -12,14 +12,14 @@ E :: error { Bad, Empty } -parse :: (n: i32) -> Tuple(i32, i32) !E { +parse :: (n: i32) -> (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} } // Multi-value `try` in a multi-value caller — propagates {undef, undef, tag}. -inc :: (n: i32) -> Tuple(i32, i32) !E { +inc :: (n: i32) -> (i32, i32, !E) { v, b := try parse(n); return .(v + 1, b + 1); } diff --git a/examples/errors/1019-errors-failable-discard-reject.sx b/examples/errors/1019-errors-failable-discard-reject.sx index eb37bf73..99e55683 100644 --- a/examples/errors/1019-errors-failable-discard-reject.sx +++ b/examples/errors/1019-errors-failable-discard-reject.sx @@ -13,12 +13,12 @@ E :: error { Bad, Empty } -pair :: (n: i32) -> Tuple(i32, i32) !E { +pair :: (n: i32) -> (i32, i32, !E) { if n < 0 { raise error.Bad; } return .(n, n + 1); } -parse :: (n: i32) -> i32 !E { +parse :: (n: i32) -> (i32, !E) { if n < 0 { raise error.Bad; } return n * 2; } diff --git a/examples/errors/1023-errors-tag-interpolation.sx b/examples/errors/1023-errors-tag-interpolation.sx index 3464ead7..908746d7 100644 --- a/examples/errors/1023-errors-tag-interpolation.sx +++ b/examples/errors/1023-errors-tag-interpolation.sx @@ -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; diff --git a/examples/errors/1026-errors-failable-main.sx b/examples/errors/1026-errors-failable-main.sx index e4e595b4..79ec4d46 100644 --- a/examples/errors/1026-errors-failable-main.sx +++ b/examples/errors/1026-errors-failable-main.sx @@ -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; diff --git a/examples/errors/1027-errors-failable-main-value.sx b/examples/errors/1027-errors-failable-main-value.sx index 58a4608c..cdab14a1 100644 --- a/examples/errors/1027-errors-failable-main-value.sx +++ b/examples/errors/1027-errors-failable-main-value.sx @@ -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 diff --git a/examples/errors/1028-errors-failable-or-chain.sx b/examples/errors/1028-errors-failable-or-chain.sx index 0c96cb75..6a930528 100644 --- a/examples/errors/1028-errors-failable-or-chain.sx +++ b/examples/errors/1028-errors-failable-or-chain.sx @@ -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; diff --git a/examples/errors/1029-errors-failable-or-chain-propagate.sx b/examples/errors/1029-errors-failable-or-chain-propagate.sx index fa5f4c87..5668b97b 100644 --- a/examples/errors/1029-errors-failable-or-chain-propagate.sx +++ b/examples/errors/1029-errors-failable-or-chain-propagate.sx @@ -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; } diff --git a/examples/errors/1036-errors-failable-smoke.sx b/examples/errors/1036-errors-failable-smoke.sx index e324dbdb..9c65159b 100644 --- a/examples/errors/1036-errors-failable-smoke.sx +++ b/examples/errors/1036-errors-failable-smoke.sx @@ -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,7 +23,7 @@ sm_check :: (ok: bool) -> ! { } // multi-value, inferred set: `try` propagates; SCC absorbs SmokeErr -sm_pair :: (a: i32, b: i32) -> Tuple(i32, i32) ! { +sm_pair :: (a: i32, b: i32) -> (i32, i32, !) { x := try sm_parse(a); y := try sm_parse(b); return .(x, y); @@ -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(); } @@ -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"); } diff --git a/examples/errors/1037-errors-comptime-run-escape.sx b/examples/errors/1037-errors-comptime-run-escape.sx index e959fe26..c5e6fe86 100644 --- a/examples/errors/1037-errors-comptime-run-escape.sx +++ b/examples/errors/1037-errors-comptime-run-escape.sx @@ -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; diff --git a/examples/errors/1038-errors-comptime-run-handled.sx b/examples/errors/1038-errors-comptime-run-handled.sx index e86dc352..76399b82 100644 --- a/examples/errors/1038-errors-comptime-run-handled.sx +++ b/examples/errors/1038-errors-comptime-run-handled.sx @@ -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; diff --git a/examples/errors/1039-errors-failable-closure-literal.sx b/examples/errors/1039-errors-failable-closure-literal.sx index f4510802..ac3b061d 100644 --- a/examples/errors/1039-errors-failable-closure-literal.sx +++ b/examples/errors/1039-errors-failable-closure-literal.sx @@ -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 diff --git a/examples/errors/1040-errors-failable-closure-composition.sx b/examples/errors/1040-errors-failable-closure-composition.sx index d3934aa6..6bbbd694 100644 --- a/examples/errors/1040-errors-failable-closure-composition.sx +++ b/examples/errors/1040-errors-failable-closure-composition.sx @@ -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 diff --git a/examples/errors/1041-errors-failable-closure-shape-union.sx b/examples/errors/1041-errors-failable-closure-shape-union.sx index 74046ace..442fffb5 100644 --- a/examples/errors/1041-errors-failable-closure-shape-union.sx +++ b/examples/errors/1041-errors-failable-closure-shape-union.sx @@ -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 diff --git a/examples/errors/1042-errors-failable-closure-shape-union-reject.sx b/examples/errors/1042-errors-failable-closure-shape-union-reject.sx index f9d75f0e..0ead4abb 100644 --- a/examples/errors/1042-errors-failable-closure-shape-union-reject.sx +++ b/examples/errors/1042-errors-failable-closure-shape-union-reject.sx @@ -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; diff --git a/examples/errors/1043-errors-lambda-raise-annotation-hint.sx b/examples/errors/1043-errors-lambda-raise-annotation-hint.sx index 095cb8dc..20074adc 100644 --- a/examples/errors/1043-errors-lambda-raise-annotation-hint.sx +++ b/examples/errors/1043-errors-lambda-raise-annotation-hint.sx @@ -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: diff --git a/examples/errors/1044-errors-generic-failable-composition.sx b/examples/errors/1044-errors-generic-failable-composition.sx index 495a0728..635e5274 100644 --- a/examples/errors/1044-errors-generic-failable-composition.sx +++ b/examples/errors/1044-errors-generic-failable-composition.sx @@ -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; } diff --git a/examples/errors/1045-errors-closure-var-bare-slot-reject.sx b/examples/errors/1045-errors-closure-var-bare-slot-reject.sx index 8fecfb18..c23927ee 100644 --- a/examples/errors/1045-errors-closure-var-bare-slot-reject.sx +++ b/examples/errors/1045-errors-closure-var-bare-slot-reject.sx @@ -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 diff --git a/examples/errors/1046-errors-value-slot-liveness.sx b/examples/errors/1046-errors-value-slot-liveness.sx index 62ca002c..433c3b14 100644 --- a/examples/errors/1046-errors-value-slot-liveness.sx +++ b/examples/errors/1046-errors-value-slot-liveness.sx @@ -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 diff --git a/examples/errors/1047-errors-value-slot-liveness-reject.sx b/examples/errors/1047-errors-value-slot-liveness-reject.sx index b0cde6aa..2f91346a 100644 --- a/examples/errors/1047-errors-value-slot-liveness-reject.sx +++ b/examples/errors/1047-errors-value-slot-liveness-reject.sx @@ -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; } diff --git a/examples/errors/1048-errors-cleanup-absorption.sx b/examples/errors/1048-errors-cleanup-absorption.sx index 8f8064cb..d49b6f6a 100644 --- a/examples/errors/1048-errors-cleanup-absorption.sx +++ b/examples/errors/1048-errors-cleanup-absorption.sx @@ -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 diff --git a/examples/errors/1050-errors-defer-block-body.sx b/examples/errors/1050-errors-defer-block-body.sx index f20ac955..b4513158 100644 --- a/examples/errors/1050-errors-defer-block-body.sx +++ b/examples/errors/1050-errors-defer-block-body.sx @@ -9,7 +9,7 @@ E :: error { Bad } -probe :: () -> i32 !E { return 21; } +probe :: () -> (i32, !E) { return 21; } failing :: () -> !E { raise error.Bad; } run :: () { diff --git a/examples/errors/1051-errors-cleanup-closure-boundary.sx b/examples/errors/1051-errors-cleanup-closure-boundary.sx index 7fbdc4f7..1dbc42db 100644 --- a/examples/errors/1051-errors-cleanup-closure-boundary.sx +++ b/examples/errors/1051-errors-cleanup-closure-boundary.sx @@ -24,7 +24,7 @@ E :: error { Bad } failing :: () -> !E { raise error.Bad; } -recover :: () -> i32 !E { return 21; } +recover :: () -> (i32, !E) { return 21; } work :: () { defer { diff --git a/examples/errors/1053-errors-nested-lambda-liveness-reject.sx b/examples/errors/1053-errors-nested-lambda-liveness-reject.sx index 806b440d..243eb3a3 100644 --- a/examples/errors/1053-errors-nested-lambda-liveness-reject.sx +++ b/examples/errors/1053-errors-nested-lambda-liveness-reject.sx @@ -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; } diff --git a/examples/errors/1054-errors-backtick-reserved-binding.sx b/examples/errors/1054-errors-backtick-reserved-binding.sx index 4ac6a24f..8528e0e7 100644 --- a/examples/errors/1054-errors-backtick-reserved-binding.sx +++ b/examples/errors/1054-errors-backtick-reserved-binding.sx @@ -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; diff --git a/examples/errors/1055-errors-enum-value-failable-error-slot.sx b/examples/errors/1055-errors-enum-value-failable-error-slot.sx index 5b406996..6e74c1a3 100644 --- a/examples/errors/1055-errors-enum-value-failable-error-slot.sx +++ b/examples/errors/1055-errors-enum-value-failable-error-slot.sx @@ -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; diff --git a/examples/errors/1056-errors-enum-value-failable-tuple-and-comptime.sx b/examples/errors/1056-errors-enum-value-failable-tuple-and-comptime.sx index 49a629d0..dfc605dd 100644 --- a/examples/errors/1056-errors-enum-value-failable-tuple-and-comptime.sx +++ b/examples/errors/1056-errors-enum-value-failable-tuple-and-comptime.sx @@ -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} } // 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} diff --git a/examples/errors/1057-errors-negated-error-binding.sx b/examples/errors/1057-errors-negated-error-binding.sx index e07c8581..3e1ee8bd 100644 --- a/examples/errors/1057-errors-negated-error-binding.sx +++ b/examples/errors/1057-errors-negated-error-binding.sx @@ -7,7 +7,7 @@ E :: error { Boom } -f :: (fail: bool) -> i64 !E { +f :: (fail: bool) -> (i64, !E) { if fail { raise error.Boom; } return 42; } diff --git a/examples/errors/1058-errors-reexport-value-failable-channel/lib.sx b/examples/errors/1058-errors-reexport-value-failable-channel/lib.sx index 75f65d79..848f3c22 100644 --- a/examples/errors/1058-errors-reexport-value-failable-channel/lib.sx +++ b/examples/errors/1058-errors-reexport-value-failable-channel/lib.sx @@ -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; } diff --git a/examples/errors/1060-errors-named-tuple-failable.sx b/examples/errors/1060-errors-named-tuple-failable.sx index 6ec048cd..10461004 100644 --- a/examples/errors/1060-errors-named-tuple-failable.sx +++ b/examples/errors/1060-errors-named-tuple-failable.sx @@ -1,4 +1,4 @@ -// A failable function returning a NAMED tuple value `-> Tuple(x: A, y: B) !E` +// A failable function returning a NAMED tuple value `-> (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 @@ -12,7 +12,7 @@ E :: error { Bad } -two :: (n: i64) -> Tuple(x: i64, y: i64) !E { +two :: (n: i64) -> (x: i64, y: i64, !E) { if n < 0 { raise error.Bad; } return .(x = n, y = n + 1); } diff --git a/examples/errors/1062-errors-value-failable-trailing-expr.sx b/examples/errors/1062-errors-value-failable-trailing-expr.sx index 090d990e..798962db 100644 --- a/examples/errors/1062-errors-value-failable-trailing-expr.sx +++ b/examples/errors/1062-errors-value-failable-trailing-expr.sx @@ -1,4 +1,4 @@ -// Value-carrying failable functions (`-> T !E`) whose body ends in a trailing +// Value-carrying failable functions (`-> (T, !E)`) whose body ends in a trailing // success EXPRESSION (no explicit `return`) must set the success error slot to // 0 — the caller's `catch` must NOT fire and the success value must be intact. // Regression (issue 0190): `lowerValueBody` used to `coerceToType`+`ret` the @@ -10,16 +10,16 @@ E :: error { Bad } // Single-value trailing-expression success. -val :: () -> i64 !E { 99 } +val :: () -> (i64, !E) { 99 } // String trailing-expression success. -sval :: () -> string !E { "hi" } +sval :: () -> (string, !E) { "hi" } // Multi-value (tuple) trailing-expression success. -mval :: () -> Tuple(i64, i64) !E { .(1, 2) } +mval :: () -> (i64, i64, !E) { .(1, 2) } // A real error still propagates through the value-failable channel. -fval :: (n: i64) -> i64 !E { +fval :: (n: i64) -> (i64, !E) { if n < 0 { raise error.Bad; } n + 1 } diff --git a/examples/errors/1063-errors-generic-value-failable-trailing-expr.sx b/examples/errors/1063-errors-generic-value-failable-trailing-expr.sx index 19bcc500..132e823f 100644 --- a/examples/errors/1063-errors-generic-value-failable-trailing-expr.sx +++ b/examples/errors/1063-errors-generic-value-failable-trailing-expr.sx @@ -1,4 +1,4 @@ -// Generic value-carrying failable functions (`($T) -> T !E`) whose body ends in +// Generic value-carrying failable functions (`($T) -> (T, !E)`) whose body ends in // a trailing success EXPRESSION (no explicit `return`) must set the success // error slot to 0 — the caller's `catch` must NOT fire and the value must be // intact across instantiations (i64 / string / struct), and `or` must yield the @@ -14,10 +14,10 @@ E :: error { Bad } Point :: struct { x: i64; y: i64; } // Generic trailing-expression success — instantiated at i64 / string / struct. -gen :: ($T: Type, v: T) -> T !E { v } +gen :: ($T: Type, v: T) -> (T, !E) { v } // Generic that RAISES — the caller's catch must still fire. -gfail :: ($T: Type, v: T, bad: bool) -> T !E { +gfail :: ($T: Type, v: T, bad: bool) -> (T, !E) { if bad { raise error.Bad; } v } diff --git a/examples/protocols/0415-protocols-protocols.sx b/examples/protocols/0415-protocols-protocols.sx index 77a039f5..d2c86532 100644 --- a/examples/protocols/0415-protocols-protocols.sx +++ b/examples/protocols/0415-protocols-protocols.sx @@ -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,7 +159,7 @@ 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) -> Tuple(i32, i32) ! { +sm_pair :: (a: i32, b: i32) -> (i32, i32, !) { x := try sm_parse(a); y := try sm_parse(b); return .(x, y); @@ -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; } diff --git a/examples/types/0204-types-multi-return-failable.sx b/examples/types/0204-types-multi-return-failable.sx index 95fe481a..19fda44c 100644 --- a/examples/types/0204-types-multi-return-failable.sx +++ b/examples/types/0204-types-multi-return-failable.sx @@ -1,7 +1,7 @@ // A multi-return signature may carry an error channel as its LAST slot: // `-> (i32, bool, !)` returns two values OR fails. This reuses the existing // value-carrying-failable machinery — the error is always the final slot. A -// single-value failable `-> (T, !)` (one value + error) is exactly `-> T !`, +// single-value failable `-> (T, !)` (one value + error) is exactly `-> (T, !)`, // NOT a multi-return. // // Consume it like any failable: `catch` / `or` / a guarded destructure or diff --git a/library/modules/std/cli.sx b/library/modules/std/cli.sx index 0aeb5b1f..11f4c6af 100644 --- a/library/modules/std/cli.sx +++ b/library/modules/std/cli.sx @@ -263,7 +263,7 @@ is_long_flag :: (s: string) -> bool { // Parse `args` (the logical argv) against the `commands` table, writing // the offending token into `diag` on the error path. See the section // 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 ── if args.len < 2 { diag.index = if args.len == 0 then -1 else 0; diff --git a/library/modules/std/event.sx b/library/modules/std/event.sx index ac7779d3..9304b210 100644 --- a/library/modules/std/event.sx +++ b/library/modules/std/event.sx @@ -116,7 +116,7 @@ Loop :: struct { // long-lived-container rule). own: Allocator; - init :: () -> Loop !EventErr { + init :: () -> (Loop, !EventErr) { e := ep.ep_create(); if e < 0 { raise error.Init; } return Loop.{ epfd = e, regs = .{}, own = context.allocator }; @@ -216,7 +216,7 @@ Loop :: struct { // Fill `out` with ready events, waiting at most `timeout_ms` // (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]ep.EpollEvent = ---; cap : i64 = 64; if xx out.len < cap { cap = xx out.len; } @@ -254,7 +254,7 @@ Loop :: struct { Loop :: struct { kq: i32 = -1; - init :: () -> Loop !EventErr { + init :: () -> (Loop, !EventErr) { q := kqb.kqueue(); if q < 0 { raise error.Init; } return Loop.{ kq = q }; @@ -298,7 +298,7 @@ Loop :: struct { // Fill `out` with ready events, waiting at most `timeout_ms` // (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 = ---; cap : i64 = 64; if xx out.len < cap { cap = xx out.len; } diff --git a/library/modules/std/http.sx b/library/modules/std/http.sx index 2430fad5..98588388 100644 --- a/library/modules/std/http.sx +++ b/library/modules/std/http.sx @@ -263,7 +263,7 @@ Server :: struct { ctx: usize = 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); if lfd < 0 { raise error.Bind; } one : i32 = 1; diff --git a/library/modules/std/io.sx b/library/modules/std/io.sx index 9c40f9dd..b9510dd8 100644 --- a/library/modules/std/io.sx +++ b/library/modules/std/io.sx @@ -172,7 +172,7 @@ async :: ufcs (io: Io, worker: Closure() -> $R) -> *Future($R) { // resumes it. Re-checks state after the wake (the worker set `.ready` before // waking). A worker that finished BEFORE `await` leaves `.ready`, so no park, no // lost wakeup. -await :: ufcs (f: *Future($R)) -> $R !IoErr { +await :: ufcs (f: *Future($R)) -> ($R, !IoErr) { if f.canceled.load(.acquire) { raise error.Canceled; } if f.state == .pending { // ONE awaiter per future (M:1): the single `park` slot records one parked diff --git a/library/modules/std/json.sx b/library/modules/std/json.sx index c4afd267..a67635cb 100644 --- a/library/modules/std/json.sx +++ b/library/modules/std/json.sx @@ -321,7 +321,7 @@ write_object :: (obj: Object, sink: *Sink) -> !JsonError { // bytes written. Raises `error.Overflow` if `dst` is too small (the // partial contents of `dst` are then undefined — nothing is truncated // 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 }; try write_value(v, @sink); 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 // 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 >= 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' @@ -450,7 +450,7 @@ Parser :: struct { // 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. - 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; } v := 0; k := 0; @@ -464,7 +464,7 @@ Parser :: struct { // Decode the escaped string body in [start, end) into `out`, returning // the decoded byte length. Pass 1 (in parse_string) guarantees there is // 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; i := start; while i < end { @@ -511,7 +511,7 @@ Parser :: struct { // 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` // ends just past the closing quote. - parse_string :: (self: *Parser) -> string !JsonParseError { + parse_string :: (self: *Parser) -> (string, !JsonParseError) { self.pos += 1; // consume opening quote start := self.pos; has_escape := false; @@ -547,7 +547,7 @@ Parser :: struct { // Parse an i64 integer (optional '-', then digits). Rejects leading // zeros, a fraction/exponent tail, and any value outside i64 — all // `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 // representable positive i64 literal. `min_div10` is `MIN / 10` // 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_array :: (self: *Parser) -> Value !JsonParseError { + parse_array :: (self: *Parser) -> (Value, !JsonParseError) { self.pos += 1; // consume '[' arr : Array = .{}; self.skip_ws(); @@ -609,7 +609,7 @@ Parser :: struct { // Parse an object starting at '{'. Keys must be strings; insertion // 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 '{' obj : Object = .{}; self.skip_ws(); @@ -640,7 +640,7 @@ Parser :: struct { } // Parse any single value (after skipping leading whitespace). - parse_value :: (self: *Parser) -> Value !JsonParseError { + parse_value :: (self: *Parser) -> (Value, !JsonParseError) { self.skip_ws(); if self.pos >= self.src.len { raise error.UnexpectedEnd; } c := self.src[self.pos]; @@ -659,7 +659,7 @@ Parser :: struct { // `alloc` for composite nodes and decoded (escaped) strings. Un-escaped // string values are VIEWS into `src` and are valid only while `src` lives. // 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 }; v := try p.parse_value(); p.skip_ws(); diff --git a/library/modules/std/sched.sx b/library/modules/std/sched.sx index f4330621..df0d5058 100644 --- a/library/modules/std/sched.sx +++ b/library/modules/std/sched.sx @@ -1079,7 +1079,7 @@ go :: ufcs (self: *Scheduler, work: Closure() -> $R) -> *Task($R) { // 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 // 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.state == .pending { // ONE waiter per task (enforced). A `Task` holds a single `waiter` slot; diff --git a/library/modules/std/socket.sx b/library/modules/std/socket.sx index d96710a6..aac317d7 100644 --- a/library/modules/std/socket.sx +++ b/library/modules/std/socket.sx @@ -93,7 +93,7 @@ SockErr :: error { // Accept one pending connection on a nonblocking listener. A connection // that died between queueing and accept (ECONNABORTED) is skipped, not // surfaced — the listener is fine. -accept_nb :: (fd: i32) -> i32 !SockErr { +accept_nb :: (fd: i32) -> (i32, !SockErr) { while true { c := accept(fd, null, null); 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 // 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 { n := read(fd, buf, cap); 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 // 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 { n := write(fd, buf, len); if n >= 0 { return xx n; } diff --git a/library/modules/std/thread.sx b/library/modules/std/thread.sx index 7e8de867..40e29bf3 100644 --- a/library/modules/std/thread.sx +++ b/library/modules/std/thread.sx @@ -106,7 +106,7 @@ Thread :: struct { // `entry` is the C->sx boundary: abi(.c), fabricates its own // 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 = .{}; if pthread_create(@t.handle, null, entry, arg) != 0 { raise error.Spawn; } return t; @@ -144,7 +144,7 @@ Pool :: struct { // Heap-allocate (the pool must never move: workers hold its address, // 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; p : *Pool = xx alloc.alloc_bytes(size_of(Pool)); p.* = Pool.{}; diff --git a/library/vendors/sqlite/sqlite.sx b/library/vendors/sqlite/sqlite.sx index cfbef864..d303ea6e 100644 --- a/library/vendors/sqlite/sqlite.sx +++ b/library/vendors/sqlite/sqlite.sx @@ -461,7 +461,7 @@ SqliteStmt :: struct { // ── execution ── // SQLITE_ROW / SQLITE_DONE on success; anything else raises with the // detail left in the connection's errmsg. - step :: (self: *SqliteStmt) -> i32 !SqliteErr { + step :: (self: *SqliteStmt) -> (i32, !SqliteErr) { rc := sqlite3_step(self.handle); if rc != SQLITE_ROW and rc != SQLITE_DONE { raise error.Step; } return rc; @@ -564,7 +564,7 @@ ColumnMeta :: struct { Sqlite :: struct { handle: usize; - open :: (path: string) -> Sqlite !SqliteErr { + open :: (path: string) -> (Sqlite, !SqliteErr) { h : usize = 0; rc := sqlite3_open(to_cstring(path), @h); if rc != SQLITE_OK { @@ -574,7 +574,7 @@ Sqlite :: struct { return Sqlite.{ handle = h }; } - open_v2 :: (path: string, flags: i32) -> Sqlite !SqliteErr { + open_v2 :: (path: string, flags: i32) -> (Sqlite, !SqliteErr) { h : usize = 0; rc := sqlite3_open_v2(to_cstring(path), @h, flags, 0); if rc != SQLITE_OK { @@ -603,7 +603,7 @@ Sqlite :: struct { return; } - prepare :: (self: *Sqlite, sql: string) -> SqliteStmt !SqliteErr { + prepare :: (self: *Sqlite, sql: string) -> (SqliteStmt, !SqliteErr) { sh : usize = 0; rc := sqlite3_prepare_v2(self.handle, sql.ptr, xx sql.len, @sh, 0); if rc != SQLITE_OK { raise error.Prepare; } @@ -611,7 +611,7 @@ Sqlite :: struct { } // prepare with SQLITE_PREPARE_* flags (e.g. PERSISTENT for the // statement cache a storage layer keeps hot). - prepare_v3 :: (self: *Sqlite, sql: string, flags: u32) -> SqliteStmt !SqliteErr { + prepare_v3 :: (self: *Sqlite, sql: string, flags: u32) -> (SqliteStmt, !SqliteErr) { sh : usize = 0; rc := sqlite3_prepare_v3(self.handle, sql.ptr, xx sql.len, flags, @sh, 0); if rc != SQLITE_OK { raise error.Prepare; } @@ -685,7 +685,7 @@ Sqlite :: struct { } // Schema introspection for one column of "main".`table`. - table_column_metadata :: (self: *Sqlite, table: string, column: string) -> ColumnMeta !SqliteErr { + table_column_metadata :: (self: *Sqlite, table: string, column: string) -> (ColumnMeta, !SqliteErr) { dt : usize = 0; cs : usize = 0; nn : i32 = 0; @@ -703,7 +703,7 @@ Sqlite :: struct { // ── serialization ── // The whole "main" database as bytes (a valid database image). - serialize :: (self: *Sqlite) -> string !SqliteErr { + serialize :: (self: *Sqlite) -> (string, !SqliteErr) { size : i64 = 0; p := sqlite3_serialize(self.handle, to_cstring("main"), @size, 0); if p == null { raise error.Serialize; } @@ -734,7 +734,7 @@ Sqlite :: struct { SqliteBlob :: struct { handle: usize; - open :: (db: *Sqlite, table: string, column: string, rowid: i64, writable: bool) -> SqliteBlob !SqliteErr { + open :: (db: *Sqlite, table: string, column: string, rowid: i64, writable: bool) -> (SqliteBlob, !SqliteErr) { h : usize = 0; rc := sqlite3_blob_open(db.handle, to_cstring("main"), to_cstring(table), to_cstring(column), rowid, if writable then 1 else 0, @h); @@ -751,7 +751,7 @@ SqliteBlob :: struct { bytes :: (self: *SqliteBlob) -> i32 { return sqlite3_blob_bytes(self.handle); } - read :: (self: *SqliteBlob, offset: i32, n: i32) -> string !SqliteErr { + read :: (self: *SqliteBlob, offset: i32, n: i32) -> (string, !SqliteErr) { len : i64 = n; raw : [*]u8 = xx context.allocator.alloc_bytes(len + 1); rc := sqlite3_blob_read(self.handle, raw, n, offset); @@ -778,7 +778,7 @@ SqliteBlob :: struct { SqliteBackup :: struct { handle: usize; - init :: (dst: *Sqlite, src: *Sqlite) -> SqliteBackup !SqliteErr { + init :: (dst: *Sqlite, src: *Sqlite) -> (SqliteBackup, !SqliteErr) { h := sqlite3_backup_init(dst.handle, to_cstring("main"), src.handle, to_cstring("main")); if h == 0 { raise error.Backup; } return SqliteBackup.{ handle = h }; diff --git a/readme.md b/readme.md index 8a180d0a..ee853153 100644 --- a/readme.md +++ b/readme.md @@ -604,7 +604,7 @@ main :: () { Tasks complete in deadline order, not spawn or await order. The runtime offers: -- **`go(work) -> *Task($R)`** / **`wait() -> R !TaskErr`** / **`cancel()`** — the +- **`go(work) -> *Task($R)`** / **`wait() -> (R, !TaskErr)`** / **`cancel()`** — the task layer. `wait` rides the `!` error channel so a cancel surfaces as `error.Canceled`. - **`spawn`**, **`yield_now`**, **`suspend_self`**, **`wake`** — the raw fiber diff --git a/specs.md b/specs.md index f769dddc..2d461a8c 100644 --- a/specs.md +++ b/specs.md @@ -903,7 +903,7 @@ VALUE: it is represented internally by a reused tuple TypeId (same ABI), but it is valid ONLY in a function/closure return position — a parameter, field, or variable annotation `x: (A, B)` is rejected (use `Tuple(…)` for a tuple value). A single-value `-> (T, !)` (one value + error) is NOT a multi-return; it is -exactly the failable `-> T !`. +exactly the failable `-> (T, !)`. ```sx divmod :: (a: i64, b: i64) -> (i64, i64) { return a / b, a % b; } @@ -1886,7 +1886,7 @@ name :: (params) -> return_type { A trailing `!` in the return type marks the function **failable** — it adds a separate error channel alongside the normal returns. The `!` sits **outside** -the tuple: `-> T !` (one value), `-> Tuple(T1, T2) !` (multi value), `-> !` +the tuple: `-> (T, !)` (one value), `-> (T1, T2, !)` (multi value), `-> !` (void). The `!` is not a wrapper around the value; it is one more return slot. See [§12 Error Handling](#12-error-handling). @@ -3154,7 +3154,7 @@ main :: () { `main` takes no arguments. Its return type may be any of: void (`()`, `-> ()`, `-> void`, or no annotation), an integer type (POSIX exit code), -`-> !` (pure failable), or `-> int_type !` (value-carrying failable). +`-> !` (pure failable), or `-> (int_type, !)` (value-carrying failable). The exit code is `0` for void / `-> !` success, the integer return truncated to `u8` otherwise. An error that escapes a failable `main` prints the unhandled-error header + return trace to stderr and exits `1`. @@ -3165,11 +3165,12 @@ See [§12 Error Handling](#12-error-handling). ## 12. Error Handling sx models recoverable errors as a **separate return channel**, not a wrapped -result type. A trailing `!` in a function's return type adds one extra return -slot — a `u32` error tag — alongside the normal value slots. This keeps sx's -native multi-return ergonomics: `-> Tuple(i32, i64) !` is a function returning -two values *and* an error, with no tuple-in-a-wrapper. The `!` sits **outside** -the `Tuple`. +result type. A `!` as the **last slot** of the parenthesized result list adds one +extra return slot — a `u32` error tag — alongside the normal value slots. This +keeps sx's native multi-return ergonomics: `-> (i32, i64, !)` is a function +returning two values *and* an error, with no tuple-in-a-wrapper. A single-value +failable is `-> (T, !)`; an error-only failable is `-> !`. (There is no bare +`-> T !` spelling — the error channel always rides inside the `(…, !)` list.) This section is the canonical surface reference. The design rationale, trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`. @@ -3177,10 +3178,10 @@ trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`. ### Failable signatures ```sx -parse_digit :: (s: string) -> i32 ! { ... } // one value + error -parse :: (s: string) -> Tuple(i32, i64) ! { ... } // multi-value + error -must_init :: () -> ! { ... } // pure failable, no value -divide :: (a: i32, b: i32) -> i32 !MathErr { ... } // named set +parse_digit :: (s: string) -> (i32, !) { ... } // one value + error +parse :: (s: string) -> (i32, i64, !) { ... } // multi-value + error +must_init :: () -> ! { ... } // pure failable, no value +divide :: (a: i32, b: i32) -> (i32, !MathErr) { ... } // named set ``` The `!` is always the **last** slot. `0` in the error slot means "no error"; @@ -3195,7 +3196,7 @@ Two forms of error set: ParseErr :: error { BadDigit, Overflow, Empty }; // Inferred set — bare `!` collects whatever tags the body raises. -quick :: () -> i32 ! { +quick :: () -> (i32, !) { if cond raise error.SomeAdHocTag; // mints into the inferred set return 0; } @@ -3393,14 +3394,14 @@ On success exit (fall-through, `return`, `break` / `continue` without an error) it is skipped — only `defer` runs. ```sx -make_handle :: () -> Handle ! { +make_handle :: () -> (Handle, !) { h := try open(); onfail close(h); // close ONLY on a subsequent failure try configure(h); // fails → onfail runs → close(h) return h; // success → onfail skipped; caller owns h } -open :: (path: string) -> Handle ! { +open :: (path: string) -> (Handle, !) { h := try sys_open(path); onfail (e) { log.warn("init failed for {}: {}", path, e); sys_close(h); } ... @@ -3421,9 +3422,9 @@ function, or at top level, is rejected. - **Explicit annotation required.** A closure literal's value type is inferred as today, but if its body raises or `try`-escapes, the `!` channel is **not** - inferred — declare it (`closure((x: i32) -> i32 ! { ... })`). This keeps + inferred — declare it (`closure((x: i32) -> (i32, !) { ... })`). This keeps adding a `raise` from silently changing a lambda's type. -- **Program-wide union per shape.** All `Closure() -> T !` occurrences +- **Program-wide union per shape.** All `Closure() -> (T, !)` occurrences with the same signature share one inferred-set node; the SCC pass unions every closure flowing into any matching slot. - **FFI boundary.** A failable closure cannot be assigned to a non-failable diff --git a/src/ast.zig b/src/ast.zig index 40ce382c..8d0f68b3 100644 --- a/src/ast.zig +++ b/src/ast.zig @@ -876,7 +876,7 @@ pub const TupleTypeExpr = struct { /// rejects it anywhere else), and its result is consumed only by destructuring /// (`a, b := f()`), never bound to a single value. Same shape as a tuple type so /// the resolver can reuse the field-resolution path. The single-value `(T, !)` -/// (one value + error) is NOT this — it is a plain failable, `-> T !`. +/// (one value + error) is NOT this — it is a plain failable. pub const ReturnTypeExpr = struct { field_types: []const *Node, field_names: ?[]const []const u8, // null for positional diff --git a/src/parser.test.zig b/src/parser.test.zig index a31ffe01..6f2a5387 100644 --- a/src/parser.test.zig +++ b/src/parser.test.zig @@ -341,36 +341,21 @@ test "parser: .(x) is a 1-tuple, .() is empty" { try std.testing.expectEqual(@as(usize, 0), v2.data.tuple_literal.elements.len); } -// `-> T !` folds to the same `(T, !)` representation: tuple_type_expr whose -// last field is an error_type_expr. -test "parser: -> T ! folds to (T, !) tuple_type_expr" { +// The legacy bare trailing-`!` spelling `-> T !` was removed — the canonical +// failable result list is `-> (T, !)`. The bare form is now a parse error. +test "parser: legacy bare `-> T !` is rejected" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var parser = Parser.init(arena.allocator(), "f :: () -> i64 ! { 0 }"); - const root = try parser.parse(); - const rt = root.data.root.decls[0].data.fn_decl.return_type.?; - try std.testing.expect(rt.data == .tuple_type_expr); - const fields = rt.data.tuple_type_expr.field_types; - try std.testing.expectEqual(@as(usize, 2), fields.len); - try std.testing.expect(fields[0].data == .type_expr); - try std.testing.expect(fields[1].data == .error_type_expr); - try std.testing.expect(fields[1].data.error_type_expr.name == null); + try std.testing.expectError(error.ParseError, parser.parse()); } -// `-> Tuple(T1, T2) !` flattens to (T1, T2, !). -test "parser: -> Tuple(A, B) ! flattens to (A, B, !)" { +// Likewise the legacy `-> Tuple(A, B) !` spelling — write `-> (A, B, !)`. +test "parser: legacy bare `-> Tuple(A, B) !` is rejected" { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var parser = Parser.init(arena.allocator(), "f :: () -> Tuple(i64, i32) !ParseErr { 0 }"); - const root = try parser.parse(); - const rt = root.data.root.decls[0].data.fn_decl.return_type.?; - try std.testing.expect(rt.data == .tuple_type_expr); - const fields = rt.data.tuple_type_expr.field_types; - try std.testing.expectEqual(@as(usize, 3), fields.len); - try std.testing.expect(fields[0].data == .type_expr); - try std.testing.expect(fields[1].data == .type_expr); - try std.testing.expect(fields[2].data == .error_type_expr); - try std.testing.expectEqualStrings("ParseErr", fields[2].data.error_type_expr.name.?); + try std.testing.expectError(error.ParseError, parser.parse()); } // `-> !` (void + error) stays a bare error_type_expr — the trailing-`!` fold diff --git a/src/parser.zig b/src/parser.zig index 369005a4..9b72ab5c 100644 --- a/src/parser.zig +++ b/src/parser.zig @@ -431,66 +431,27 @@ pub const Parser = struct { return self.fail("expected ':', '=', ';', or 'extern' after type annotation"); } - /// Parse a function/method/lambda return type, folding a trailing `!` - /// error channel that sits OUTSIDE the value type into the same - /// representation the inline `(T, !)` form produces. + /// Parse a function/method/lambda/closure/fn-pointer return type. /// - /// `-> T !` ⇒ tuple_type_expr { [T, error_type_expr] } (== `(T, !)`) - /// `-> Tuple(A, B) !` ⇒ tuple_type_expr { [A, B, error_type_expr] } (== `(A, B, !)`) - /// `-> !` ⇒ error_type_expr (bare; handled by parseTypeExpr directly) + /// The canonical failable / multi-return spelling wraps the result list in + /// parens: `-> (T, !)`, `-> (A, B, !)`, `-> (x: A, y: B, !)` — the error + /// channel is the last slot. A bare `-> !` (error-only, no value) is parsed + /// by `parseTypeExpr` as an `error_type_expr` and is unaffected. /// - /// The old inline `(T, !)` / `(T1, T2, !)` forms keep working unchanged — - /// this only ADDS the trailing-`!`-after-the-type spelling. + /// The legacy trailing-`!`-after-the-value-type spelling (`-> T !`, + /// `-> Tuple(A, B) !`) is REJECTED — it was a redundant second spelling of + /// `(T, !)` / `(A, B, !)` and is no longer accepted. fn parseFnReturnType(self: *Parser) anyerror!*Node { - const start = self.current.loc.start; const ty = try self.parseTypeExpr(); - // A trailing `!` (optionally `!Named`) after the return TYPE denotes the - // error channel sitting OUTSIDE the value type. A bare `-> !` is already - // an error_type_expr (no value), so a `!` after one would be a doubled - // error channel — leave it for the normal "unexpected token" path. - if (self.current.tag != .bang or ty.data == .error_type_expr) return ty; - - self.advance(); // skip '!' - var set_name: ?[]const u8 = null; - if (self.current.tag == .identifier) { - set_name = self.tokenSlice(self.current); - self.advance(); + // A trailing `!` after a VALUE return type is the removed legacy + // spelling. (`-> !` already parsed to an error_type_expr above, so a + // `!` after one would be a doubled channel — leave that to the normal + // "unexpected token" path.) + if (self.current.tag == .bang and ty.data != .error_type_expr) { + return self.fail("a failable return is written `(T, !)` — or `(A, B, !)` for multiple values — not `T !`"); } - const err_node = try self.createNode(start, .{ .error_type_expr = .{ .name = set_name } }); - - // Build the value+error result list. If the value type is itself a - // tuple — `Tuple(A, B)` (positional) or `Tuple(x: A, y: B)` (named) — - // flatten its fields and append the error channel, so `-> Tuple(A,B) !` - // is identical to `(A, B, !)` and `-> Tuple(x: A, y: B) !` keeps the - // `x`/`y` names on the flattened value fields. The value-return lowering - // inserts the value slots FLAT into the result tuple, so the result type - // must list the value fields flat too — wrapping a named tuple as - // `{ {A,B}, err }` would miscompile the flat 2-tuple insert. Otherwise - // wrap the single value as `(T, !)`. - var fields = std.ArrayList(*Node).empty; - var names = std.ArrayList([]const u8).empty; - var has_names = false; - if (ty.data == .tuple_type_expr) { - const tt = ty.data.tuple_type_expr; - for (tt.field_types) |f| try fields.append(self.allocator, f); - if (tt.field_names) |fn_names| { - has_names = true; - for (fn_names) |nm| try names.append(self.allocator, nm); - // The trailing error channel needs a placeholder name so the - // names slice stays 1:1 with field_types. It is identified by - // position (last field), never by this name (see - // lower/error.zig errorChannelOf). - try names.append(self.allocator, "!"); - } - } else { - try fields.append(self.allocator, ty); - } - try fields.append(self.allocator, err_node); - return try self.createNode(start, .{ .tuple_type_expr = .{ - .field_types = try fields.toOwnedSlice(self.allocator), - .field_names = if (has_names) try names.toOwnedSlice(self.allocator) else null, - } }); + return ty; } fn parseTypeExpr(self: *Parser) anyerror!*Node { @@ -675,10 +636,9 @@ pub const Parser = struct { } try self.expect(.r_paren); if (self.current.tag == .arrow) { - // '->' present: function type. Accept a trailing `!`/`!Named` - // error channel after the return type (`(i64) -> i64 !E`), folded - // to the SAME `(T, !)` / `(A, B, !)` representation the inline form - // produces — the old `-> (T, !)` spelling keeps working too. + // '->' present: function type. A failable return is the canonical + // parenthesized list `(i64) -> (i64, !E)` (parseFnReturnType + // rejects the bare `-> i64 !E` spelling). self.advance(); // skip '->' const return_type = try self.parseFnReturnType(); const abi = try self.parseOptionalAbi(); @@ -848,10 +808,9 @@ pub const Parser = struct { var return_type: ?*Node = null; if (self.current.tag == .arrow) { self.advance(); - // Accept a trailing `!`/`!Named` error channel after the - // closure return type (`Closure(i64) -> i64 !E`, `... -> T !`), - // folded to the same `(T, !)` / `(A, B, !)` representation; the - // old `-> (T, !)` form keeps working. + // A failable closure return is the canonical parenthesized + // list `Closure(i64) -> (i64, !E)` (parseFnReturnType rejects + // the bare `Closure(i64) -> i64 !E` spelling). return_type = try self.parseFnReturnType(); } return try self.createNode(start, .{ .closure_type_expr = .{ @@ -5020,8 +4979,8 @@ test "parse bare failable return: named `!Foo`" { try std.testing.expectEqualStrings("ParseErr", rt.data.error_type_expr.name.?); } -test "parse failable with inferred `!` (new `-> T !` form)" { - const source = "f :: () -> i32 ! { 0; }"; +test "parse single-value failable `-> (T, !)`" { + const source = "f :: () -> (i32, !) { 0; }"; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var parser = Parser.init(arena.allocator(), source); @@ -5036,20 +4995,28 @@ test "parse failable with inferred `!` (new `-> T !` form)" { try std.testing.expect(fields[1].data.error_type_expr.name == null); } -test "parse failable with named `!Foo` (new `-> Tuple(...) !` form)" { - const source = "f :: () -> Tuple(i32, i64) !ParseErr { 0; }"; +test "parse multi-value named failable `-> (A, B, !Foo)`" { + const source = "f :: () -> (i32, i64, !ParseErr) { 0; }"; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var parser = Parser.init(arena.allocator(), source); const root = try parser.parse(); const rt = root.data.root.decls[0].data.fn_decl.return_type.?; - try std.testing.expect(rt.data == .tuple_type_expr); - const fields = rt.data.tuple_type_expr.field_types; + try std.testing.expect(rt.data == .return_type_expr or rt.data == .tuple_type_expr); + const fields = if (rt.data == .return_type_expr) rt.data.return_type_expr.field_types else rt.data.tuple_type_expr.field_types; try std.testing.expectEqual(@as(usize, 3), fields.len); try std.testing.expect(fields[2].data == .error_type_expr); try std.testing.expectEqualStrings("ParseErr", fields[2].data.error_type_expr.name.?); } +test "parse legacy bare failable `-> T !` is rejected" { + const source = "f :: () -> i32 ! { 0; }"; + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + var parser = Parser.init(arena.allocator(), source); + try std.testing.expectError(error.ParseError, parser.parse()); +} + test "parse old bare-paren failable `-> (!, i32)` is rejected" { const source = "f :: () -> (!, i32) { 0; }"; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); @@ -5077,11 +5044,10 @@ test "round-trip print: error-set decl" { try std.testing.expectEqualStrings(source, aw.writer.toArrayList().items); } -test "print: failable result list with pointer + named error folds to tuple repr" { - // New `-> T !` form: a single value + named error channel folds to the SAME - // internal `tuple_type_expr` the old `(*Handle, !IoErr)` spelling produced, - // so printType still renders the canonical tuple representation. - const source = "open :: () -> *Handle !IoErr { 0; }"; +test "print: failable result list with pointer + named error renders canonically" { + // A single value + named error channel `(*Handle, !IoErr)` renders back as + // the canonical parenthesized result list. + const source = "open :: () -> (*Handle, !IoErr) { 0; }"; var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); var parser = Parser.init(arena.allocator(), source); @@ -5431,7 +5397,7 @@ test "E0.3 or value-terminator: parse(s) or 0" { test "E0.3 full failable function parses end-to-end (all E0 forms)" { const source = - \\parse :: (s: string) -> i32 !ParseErr { + \\parse :: (s: string) -> (i32, !ParseErr) { \\ onfail (e) { cleanup(s); } \\ v := try inner(s) or 0; \\ w := other(s) catch (e2) { return 0; };