refactor: canonical failable syntax (T, !) — remove the bare -> T ! sugar
The trailing-`!`-after-the-value-type spelling (`-> T !`, `-> Tuple(A,B) !`) was a
redundant second way to write a failable return that the parser folded into the
same AST as the parenthesized `(T, !)` / `(A, B, !)` result list. Remove it so
there is ONE canonical spelling: the error channel always rides as the last slot
of the parenthesized list.
- parser: `parseFnReturnType` no longer folds a trailing `!` after a value type —
it rejects it with a located diagnostic ("a failable return is written `(T, !)`
… not `T !`"). This one chokepoint covers fn declarations, lambdas, fn-pointer
types `(A) -> R`, and closure types `Closure(A) -> R`. The error-ONLY `-> !` /
`-> !ErrSet` form is unaffected (parsed by parseTypeExpr as an error_type_expr).
- migrated every usage to canonical form across library/ + examples/ + issues/ +
tests/: `-> T !E` → `-> (T, !E)`; the value-carrying `-> Tuple(A, B) !` (which
FLATTENED to a multi-value failable) → `-> (A, B, !)`, preserving behavior. A
genuine single-tuple-value failable stays `-> (Tuple(A,B), !)`.
- parser unit tests: the "bare form folds" tests become "bare form is rejected";
canonical-form parse tests retained.
- docs: specs.md §12 + scattered refs and readme.md updated to the `(T, !)` form.
Behavior-preserving (the bare form was sugar for the same AST). Adversarial review
confirmed: rejection complete across all positions, every canonical form works on
both success/error paths, error-only `-> !` intact, no crashes. Full suite green
(unit tests + 850 corpus examples).
This commit is contained in:
@@ -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; }
|
||||||
|
|||||||
@@ -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 };
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -12,14 +12,14 @@
|
|||||||
|
|
||||||
E :: error { Bad, Empty }
|
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.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) -> Tuple(i32, i32) !E {
|
inc :: (n: i32) -> (i32, i32, !E) {
|
||||||
v, b := try parse(n);
|
v, b := try parse(n);
|
||||||
return .(v + 1, b + 1);
|
return .(v + 1, b + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
|
|
||||||
E :: error { Bad, Empty }
|
E :: error { Bad, Empty }
|
||||||
|
|
||||||
pair :: (n: i32) -> Tuple(i32, i32) !E {
|
pair :: (n: i32) -> (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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,7 +23,7 @@ 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) -> Tuple(i32, i32) ! {
|
sm_pair :: (a: i32, b: i32) -> (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);
|
||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 :: () {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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 }`),
|
// 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
|
// 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` fallback. Exercises the success path, a `raise` path, and an
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
E :: error { Bad }
|
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; }
|
if n < 0 { raise error.Bad; }
|
||||||
return .(x = n, y = n + 1);
|
return .(x = n, y = n + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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.
|
// 0 — the caller's `catch` must NOT fire and the success value must be intact.
|
||||||
// Regression (issue 0190): `lowerValueBody` used to `coerceToType`+`ret` the
|
// Regression (issue 0190): `lowerValueBody` used to `coerceToType`+`ret` the
|
||||||
@@ -10,16 +10,16 @@
|
|||||||
E :: error { Bad }
|
E :: error { Bad }
|
||||||
|
|
||||||
// Single-value trailing-expression success.
|
// Single-value trailing-expression success.
|
||||||
val :: () -> i64 !E { 99 }
|
val :: () -> (i64, !E) { 99 }
|
||||||
|
|
||||||
// String trailing-expression success.
|
// String trailing-expression success.
|
||||||
sval :: () -> string !E { "hi" }
|
sval :: () -> (string, !E) { "hi" }
|
||||||
|
|
||||||
// Multi-value (tuple) trailing-expression success.
|
// 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.
|
// 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; }
|
if n < 0 { raise error.Bad; }
|
||||||
n + 1
|
n + 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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
|
// intact across instantiations (i64 / string / struct), and `or` must yield the
|
||||||
@@ -14,10 +14,10 @@ E :: error { Bad }
|
|||||||
Point :: struct { x: i64; y: i64; }
|
Point :: struct { x: i64; y: i64; }
|
||||||
|
|
||||||
// Generic trailing-expression success — instantiated at i64 / string / struct.
|
// 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.
|
// 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; }
|
if bad { raise error.Bad; }
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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
|
||||||
|
|
||||||
// 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);
|
x := try sm_parse(a);
|
||||||
y := try sm_parse(b);
|
y := try sm_parse(b);
|
||||||
return .(x, y);
|
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
|
||||||
|
|
||||||
// `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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// A multi-return signature may carry an error channel as its LAST slot:
|
// A multi-return signature may carry an error channel as its LAST slot:
|
||||||
// `-> (i32, bool, !)` returns two values OR fails. This reuses the existing
|
// `-> (i32, bool, !)` returns two values OR fails. This reuses the existing
|
||||||
// value-carrying-failable machinery — the error is always the final slot. A
|
// 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.
|
// NOT a multi-return.
|
||||||
//
|
//
|
||||||
// Consume it like any failable: `catch` / `or` / a guarded destructure or
|
// Consume it like any failable: `catch` / `or` / a guarded destructure or
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ Loop :: struct {
|
|||||||
// long-lived-container rule).
|
// long-lived-container rule).
|
||||||
own: Allocator;
|
own: Allocator;
|
||||||
|
|
||||||
init :: () -> Loop !EventErr {
|
init :: () -> (Loop, !EventErr) {
|
||||||
e := ep.ep_create();
|
e := ep.ep_create();
|
||||||
if e < 0 { raise error.Init; }
|
if e < 0 { raise error.Init; }
|
||||||
return Loop.{ epfd = e, regs = .{}, own = context.allocator };
|
return Loop.{ epfd = e, regs = .{}, own = context.allocator };
|
||||||
@@ -216,7 +216,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]ep.EpollEvent = ---;
|
raw : [64]ep.EpollEvent = ---;
|
||||||
cap : i64 = 64;
|
cap : i64 = 64;
|
||||||
if xx out.len < cap { cap = xx out.len; }
|
if xx out.len < cap { cap = xx out.len; }
|
||||||
@@ -254,7 +254,7 @@ Loop :: 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 };
|
||||||
@@ -298,7 +298,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; }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
// 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
|
// waking). A worker that finished BEFORE `await` leaves `.ready`, so no park, no
|
||||||
// lost wakeup.
|
// 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.canceled.load(.acquire) { raise error.Canceled; }
|
||||||
if f.state == .pending {
|
if f.state == .pending {
|
||||||
// ONE awaiter per future (M:1): the single `park` slot records one parked
|
// ONE awaiter per future (M:1): the single `park` slot records one parked
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
// 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;
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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.{};
|
||||||
|
|||||||
20
library/vendors/sqlite/sqlite.sx
vendored
20
library/vendors/sqlite/sqlite.sx
vendored
@@ -461,7 +461,7 @@ SqliteStmt :: struct {
|
|||||||
// ── execution ──
|
// ── execution ──
|
||||||
// SQLITE_ROW / SQLITE_DONE on success; anything else raises with the
|
// SQLITE_ROW / SQLITE_DONE on success; anything else raises with the
|
||||||
// detail left in the connection's errmsg.
|
// detail left in the connection's errmsg.
|
||||||
step :: (self: *SqliteStmt) -> i32 !SqliteErr {
|
step :: (self: *SqliteStmt) -> (i32, !SqliteErr) {
|
||||||
rc := sqlite3_step(self.handle);
|
rc := sqlite3_step(self.handle);
|
||||||
if rc != SQLITE_ROW and rc != SQLITE_DONE { raise error.Step; }
|
if rc != SQLITE_ROW and rc != SQLITE_DONE { raise error.Step; }
|
||||||
return rc;
|
return rc;
|
||||||
@@ -564,7 +564,7 @@ ColumnMeta :: struct {
|
|||||||
Sqlite :: struct {
|
Sqlite :: struct {
|
||||||
handle: usize;
|
handle: usize;
|
||||||
|
|
||||||
open :: (path: string) -> Sqlite !SqliteErr {
|
open :: (path: string) -> (Sqlite, !SqliteErr) {
|
||||||
h : usize = 0;
|
h : usize = 0;
|
||||||
rc := sqlite3_open(to_cstring(path), @h);
|
rc := sqlite3_open(to_cstring(path), @h);
|
||||||
if rc != SQLITE_OK {
|
if rc != SQLITE_OK {
|
||||||
@@ -574,7 +574,7 @@ Sqlite :: struct {
|
|||||||
return Sqlite.{ handle = h };
|
return Sqlite.{ handle = h };
|
||||||
}
|
}
|
||||||
|
|
||||||
open_v2 :: (path: string, flags: i32) -> Sqlite !SqliteErr {
|
open_v2 :: (path: string, flags: i32) -> (Sqlite, !SqliteErr) {
|
||||||
h : usize = 0;
|
h : usize = 0;
|
||||||
rc := sqlite3_open_v2(to_cstring(path), @h, flags, 0);
|
rc := sqlite3_open_v2(to_cstring(path), @h, flags, 0);
|
||||||
if rc != SQLITE_OK {
|
if rc != SQLITE_OK {
|
||||||
@@ -603,7 +603,7 @@ Sqlite :: struct {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
prepare :: (self: *Sqlite, sql: string) -> SqliteStmt !SqliteErr {
|
prepare :: (self: *Sqlite, sql: string) -> (SqliteStmt, !SqliteErr) {
|
||||||
sh : usize = 0;
|
sh : usize = 0;
|
||||||
rc := sqlite3_prepare_v2(self.handle, sql.ptr, xx sql.len, @sh, 0);
|
rc := sqlite3_prepare_v2(self.handle, sql.ptr, xx sql.len, @sh, 0);
|
||||||
if rc != SQLITE_OK { raise error.Prepare; }
|
if rc != SQLITE_OK { raise error.Prepare; }
|
||||||
@@ -611,7 +611,7 @@ Sqlite :: struct {
|
|||||||
}
|
}
|
||||||
// prepare with SQLITE_PREPARE_* flags (e.g. PERSISTENT for the
|
// prepare with SQLITE_PREPARE_* flags (e.g. PERSISTENT for the
|
||||||
// statement cache a storage layer keeps hot).
|
// 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;
|
sh : usize = 0;
|
||||||
rc := sqlite3_prepare_v3(self.handle, sql.ptr, xx sql.len, flags, @sh, 0);
|
rc := sqlite3_prepare_v3(self.handle, sql.ptr, xx sql.len, flags, @sh, 0);
|
||||||
if rc != SQLITE_OK { raise error.Prepare; }
|
if rc != SQLITE_OK { raise error.Prepare; }
|
||||||
@@ -685,7 +685,7 @@ Sqlite :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Schema introspection for one column of "main".`table`.
|
// 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;
|
dt : usize = 0;
|
||||||
cs : usize = 0;
|
cs : usize = 0;
|
||||||
nn : i32 = 0;
|
nn : i32 = 0;
|
||||||
@@ -703,7 +703,7 @@ Sqlite :: struct {
|
|||||||
|
|
||||||
// ── serialization ──
|
// ── serialization ──
|
||||||
// The whole "main" database as bytes (a valid database image).
|
// The whole "main" database as bytes (a valid database image).
|
||||||
serialize :: (self: *Sqlite) -> string !SqliteErr {
|
serialize :: (self: *Sqlite) -> (string, !SqliteErr) {
|
||||||
size : i64 = 0;
|
size : i64 = 0;
|
||||||
p := sqlite3_serialize(self.handle, to_cstring("main"), @size, 0);
|
p := sqlite3_serialize(self.handle, to_cstring("main"), @size, 0);
|
||||||
if p == null { raise error.Serialize; }
|
if p == null { raise error.Serialize; }
|
||||||
@@ -734,7 +734,7 @@ Sqlite :: struct {
|
|||||||
SqliteBlob :: struct {
|
SqliteBlob :: struct {
|
||||||
handle: usize;
|
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;
|
h : usize = 0;
|
||||||
rc := sqlite3_blob_open(db.handle, to_cstring("main"), to_cstring(table), to_cstring(column),
|
rc := sqlite3_blob_open(db.handle, to_cstring("main"), to_cstring(table), to_cstring(column),
|
||||||
rowid, if writable then 1 else 0, @h);
|
rowid, if writable then 1 else 0, @h);
|
||||||
@@ -751,7 +751,7 @@ SqliteBlob :: struct {
|
|||||||
bytes :: (self: *SqliteBlob) -> i32 {
|
bytes :: (self: *SqliteBlob) -> i32 {
|
||||||
return sqlite3_blob_bytes(self.handle);
|
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;
|
len : i64 = n;
|
||||||
raw : [*]u8 = xx context.allocator.alloc_bytes(len + 1);
|
raw : [*]u8 = xx context.allocator.alloc_bytes(len + 1);
|
||||||
rc := sqlite3_blob_read(self.handle, raw, n, offset);
|
rc := sqlite3_blob_read(self.handle, raw, n, offset);
|
||||||
@@ -778,7 +778,7 @@ SqliteBlob :: struct {
|
|||||||
SqliteBackup :: struct {
|
SqliteBackup :: struct {
|
||||||
handle: usize;
|
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"));
|
h := sqlite3_backup_init(dst.handle, to_cstring("main"), src.handle, to_cstring("main"));
|
||||||
if h == 0 { raise error.Backup; }
|
if h == 0 { raise error.Backup; }
|
||||||
return SqliteBackup.{ handle = h };
|
return SqliteBackup.{ handle = h };
|
||||||
|
|||||||
@@ -604,7 +604,7 @@ main :: () {
|
|||||||
|
|
||||||
Tasks complete in deadline order, not spawn or await order. The runtime offers:
|
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
|
task layer. `wait` rides the `!` error channel so a cancel surfaces as
|
||||||
`error.Canceled`.
|
`error.Canceled`.
|
||||||
- **`spawn`**, **`yield_now`**, **`suspend_self`**, **`wake`** — the raw fiber
|
- **`spawn`**, **`yield_now`**, **`suspend_self`**, **`wake`** — the raw fiber
|
||||||
|
|||||||
35
specs.md
35
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
|
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).
|
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
|
A single-value `-> (T, !)` (one value + error) is NOT a multi-return; it is
|
||||||
exactly the failable `-> T !`.
|
exactly the failable `-> (T, !)`.
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
divmod :: (a: i64, b: i64) -> (i64, i64) { return a / b, a % b; }
|
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
|
A trailing `!` in the return type marks the function **failable** — it adds a
|
||||||
separate error channel alongside the normal returns. The `!` sits **outside**
|
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.
|
(void). The `!` is not a wrapper around the value; it is one more return slot.
|
||||||
See [§12 Error Handling](#12-error-handling).
|
See [§12 Error Handling](#12-error-handling).
|
||||||
|
|
||||||
@@ -3154,7 +3154,7 @@ main :: () {
|
|||||||
|
|
||||||
`main` takes no arguments. Its return type may be any of: void (`()`,
|
`main` takes no arguments. Its return type may be any of: void (`()`,
|
||||||
`-> ()`, `-> void`, or no annotation), an integer type (POSIX exit code),
|
`-> ()`, `-> 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
|
The exit code is `0` for void / `-> !` success, the integer return
|
||||||
truncated to `u8` otherwise. An error that escapes a failable `main`
|
truncated to `u8` otherwise. An error that escapes a failable `main`
|
||||||
prints the unhandled-error header + return trace to stderr and exits `1`.
|
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
|
## 12. Error Handling
|
||||||
|
|
||||||
sx models recoverable errors as a **separate return channel**, not a wrapped
|
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
|
result type. A `!` as the **last slot** of the parenthesized result list adds one
|
||||||
slot — a `u32` error tag — alongside the normal value slots. This keeps sx's
|
extra return slot — a `u32` error tag — alongside the normal value slots. This
|
||||||
native multi-return ergonomics: `-> Tuple(i32, i64) !` is a function returning
|
keeps sx's native multi-return ergonomics: `-> (i32, i64, !)` is a function
|
||||||
two values *and* an error, with no tuple-in-a-wrapper. The `!` sits **outside**
|
returning two values *and* an error, with no tuple-in-a-wrapper. A single-value
|
||||||
the `Tuple`.
|
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,
|
This section is the canonical surface reference. The design rationale,
|
||||||
trade-offs, and implementation breakdown live in `current/PLAN-ERR.md`.
|
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
|
### Failable signatures
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
parse_digit :: (s: string) -> i32 ! { ... } // one value + error
|
parse_digit :: (s: string) -> (i32, !) { ... } // one value + error
|
||||||
parse :: (s: string) -> Tuple(i32, i64) ! { ... } // multi-value + error
|
parse :: (s: string) -> (i32, i64, !) { ... } // multi-value + error
|
||||||
must_init :: () -> ! { ... } // pure failable, no value
|
must_init :: () -> ! { ... } // pure failable, no value
|
||||||
divide :: (a: i32, b: i32) -> i32 !MathErr { ... } // named set
|
divide :: (a: i32, b: i32) -> (i32, !MathErr) { ... } // named set
|
||||||
```
|
```
|
||||||
|
|
||||||
The `!` is always the **last** slot. `0` in the error slot means "no error";
|
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 };
|
ParseErr :: error { BadDigit, Overflow, Empty };
|
||||||
|
|
||||||
// Inferred set — bare `!` collects whatever tags the body raises.
|
// Inferred set — bare `!` collects whatever tags the body raises.
|
||||||
quick :: () -> i32 ! {
|
quick :: () -> (i32, !) {
|
||||||
if cond raise error.SomeAdHocTag; // mints into the inferred set
|
if cond raise error.SomeAdHocTag; // mints into the inferred set
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -3393,14 +3394,14 @@ On success exit (fall-through, `return`, `break` / `continue` without an error)
|
|||||||
it is skipped — only `defer` runs.
|
it is skipped — only `defer` runs.
|
||||||
|
|
||||||
```sx
|
```sx
|
||||||
make_handle :: () -> Handle ! {
|
make_handle :: () -> (Handle, !) {
|
||||||
h := try open();
|
h := try open();
|
||||||
onfail close(h); // close ONLY on a subsequent failure
|
onfail close(h); // close ONLY on a subsequent failure
|
||||||
try configure(h); // fails → onfail runs → close(h)
|
try configure(h); // fails → onfail runs → close(h)
|
||||||
return h; // success → onfail skipped; caller owns h
|
return h; // success → onfail skipped; caller owns h
|
||||||
}
|
}
|
||||||
|
|
||||||
open :: (path: string) -> Handle ! {
|
open :: (path: string) -> (Handle, !) {
|
||||||
h := try sys_open(path);
|
h := try sys_open(path);
|
||||||
onfail (e) { log.warn("init failed for {}: {}", path, e); sys_close(h); }
|
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
|
- **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**
|
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.
|
adding a `raise` from silently changing a lambda's type.
|
||||||
- **Program-wide union per shape.** All `Closure(<sig>) -> T !` occurrences
|
- **Program-wide union per shape.** All `Closure(<sig>) -> (T, !)` occurrences
|
||||||
with the same signature share one inferred-set node; the SCC pass unions
|
with the same signature share one inferred-set node; the SCC pass unions
|
||||||
every closure flowing into any matching slot.
|
every closure flowing into any matching slot.
|
||||||
- **FFI boundary.** A failable closure cannot be assigned to a non-failable
|
- **FFI boundary.** A failable closure cannot be assigned to a non-failable
|
||||||
|
|||||||
@@ -876,7 +876,7 @@ pub const TupleTypeExpr = struct {
|
|||||||
/// rejects it anywhere else), and its result is consumed only by destructuring
|
/// 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
|
/// (`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, !)`
|
/// 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 {
|
pub const ReturnTypeExpr = struct {
|
||||||
field_types: []const *Node,
|
field_types: []const *Node,
|
||||||
field_names: ?[]const []const u8, // null for positional
|
field_names: ?[]const []const u8, // null for positional
|
||||||
|
|||||||
@@ -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);
|
try std.testing.expectEqual(@as(usize, 0), v2.data.tuple_literal.elements.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
// `-> T !` folds to the same `(T, !)` representation: tuple_type_expr whose
|
// The legacy bare trailing-`!` spelling `-> T !` was removed — the canonical
|
||||||
// last field is an error_type_expr.
|
// failable result list is `-> (T, !)`. The bare form is now a parse error.
|
||||||
test "parser: -> T ! folds to (T, !) tuple_type_expr" {
|
test "parser: legacy bare `-> T !` is rejected" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), "f :: () -> i64 ! { 0 }");
|
var parser = Parser.init(arena.allocator(), "f :: () -> i64 ! { 0 }");
|
||||||
const root = try parser.parse();
|
try std.testing.expectError(error.ParseError, 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// `-> Tuple(T1, T2) !` flattens to (T1, T2, !).
|
// Likewise the legacy `-> Tuple(A, B) !` spelling — write `-> (A, B, !)`.
|
||||||
test "parser: -> Tuple(A, B) ! flattens to (A, B, !)" {
|
test "parser: legacy bare `-> Tuple(A, B) !` is rejected" {
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), "f :: () -> Tuple(i64, i32) !ParseErr { 0 }");
|
var parser = Parser.init(arena.allocator(), "f :: () -> Tuple(i64, i32) !ParseErr { 0 }");
|
||||||
const root = try parser.parse();
|
try std.testing.expectError(error.ParseError, 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.?);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// `-> !` (void + error) stays a bare error_type_expr — the trailing-`!` fold
|
// `-> !` (void + error) stays a bare error_type_expr — the trailing-`!` fold
|
||||||
|
|||||||
114
src/parser.zig
114
src/parser.zig
@@ -431,66 +431,27 @@ pub const Parser = struct {
|
|||||||
return self.fail("expected ':', '=', ';', or 'extern' after type annotation");
|
return self.fail("expected ':', '=', ';', or 'extern' after type annotation");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a function/method/lambda return type, folding a trailing `!`
|
/// Parse a function/method/lambda/closure/fn-pointer return type.
|
||||||
/// error channel that sits OUTSIDE the value type into the same
|
|
||||||
/// representation the inline `(T, !)` form produces.
|
|
||||||
///
|
///
|
||||||
/// `-> T !` ⇒ tuple_type_expr { [T, error_type_expr] } (== `(T, !)`)
|
/// The canonical failable / multi-return spelling wraps the result list in
|
||||||
/// `-> Tuple(A, B) !` ⇒ tuple_type_expr { [A, B, error_type_expr] } (== `(A, B, !)`)
|
/// parens: `-> (T, !)`, `-> (A, B, !)`, `-> (x: A, y: B, !)` — the error
|
||||||
/// `-> !` ⇒ error_type_expr (bare; handled by parseTypeExpr directly)
|
/// 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 —
|
/// The legacy trailing-`!`-after-the-value-type spelling (`-> T !`,
|
||||||
/// this only ADDS the trailing-`!`-after-the-type spelling.
|
/// `-> 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 {
|
fn parseFnReturnType(self: *Parser) anyerror!*Node {
|
||||||
const start = self.current.loc.start;
|
|
||||||
const ty = try self.parseTypeExpr();
|
const ty = try self.parseTypeExpr();
|
||||||
|
|
||||||
// A trailing `!` (optionally `!Named`) after the return TYPE denotes the
|
// A trailing `!` after a VALUE return type is the removed legacy
|
||||||
// error channel sitting OUTSIDE the value type. A bare `-> !` is already
|
// spelling. (`-> !` already parsed to an error_type_expr above, so a
|
||||||
// an error_type_expr (no value), so a `!` after one would be a doubled
|
// `!` after one would be a doubled channel — leave that to the normal
|
||||||
// error channel — leave it for the normal "unexpected token" path.
|
// "unexpected token" path.)
|
||||||
if (self.current.tag != .bang or ty.data == .error_type_expr) return ty;
|
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 !`");
|
||||||
self.advance(); // skip '!'
|
|
||||||
var set_name: ?[]const u8 = null;
|
|
||||||
if (self.current.tag == .identifier) {
|
|
||||||
set_name = self.tokenSlice(self.current);
|
|
||||||
self.advance();
|
|
||||||
}
|
}
|
||||||
const err_node = try self.createNode(start, .{ .error_type_expr = .{ .name = set_name } });
|
return ty;
|
||||||
|
|
||||||
// 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,
|
|
||||||
} });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseTypeExpr(self: *Parser) anyerror!*Node {
|
fn parseTypeExpr(self: *Parser) anyerror!*Node {
|
||||||
@@ -675,10 +636,9 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
try self.expect(.r_paren);
|
try self.expect(.r_paren);
|
||||||
if (self.current.tag == .arrow) {
|
if (self.current.tag == .arrow) {
|
||||||
// '->' present: function type. Accept a trailing `!`/`!Named`
|
// '->' present: function type. A failable return is the canonical
|
||||||
// error channel after the return type (`(i64) -> i64 !E`), folded
|
// parenthesized list `(i64) -> (i64, !E)` (parseFnReturnType
|
||||||
// to the SAME `(T, !)` / `(A, B, !)` representation the inline form
|
// rejects the bare `-> i64 !E` spelling).
|
||||||
// produces — the old `-> (T, !)` spelling keeps working too.
|
|
||||||
self.advance(); // skip '->'
|
self.advance(); // skip '->'
|
||||||
const return_type = try self.parseFnReturnType();
|
const return_type = try self.parseFnReturnType();
|
||||||
const abi = try self.parseOptionalAbi();
|
const abi = try self.parseOptionalAbi();
|
||||||
@@ -848,10 +808,9 @@ pub const Parser = struct {
|
|||||||
var return_type: ?*Node = null;
|
var return_type: ?*Node = null;
|
||||||
if (self.current.tag == .arrow) {
|
if (self.current.tag == .arrow) {
|
||||||
self.advance();
|
self.advance();
|
||||||
// Accept a trailing `!`/`!Named` error channel after the
|
// A failable closure return is the canonical parenthesized
|
||||||
// closure return type (`Closure(i64) -> i64 !E`, `... -> T !`),
|
// list `Closure(i64) -> (i64, !E)` (parseFnReturnType rejects
|
||||||
// folded to the same `(T, !)` / `(A, B, !)` representation; the
|
// the bare `Closure(i64) -> i64 !E` spelling).
|
||||||
// old `-> (T, !)` form keeps working.
|
|
||||||
return_type = try self.parseFnReturnType();
|
return_type = try self.parseFnReturnType();
|
||||||
}
|
}
|
||||||
return try self.createNode(start, .{ .closure_type_expr = .{
|
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.?);
|
try std.testing.expectEqualStrings("ParseErr", rt.data.error_type_expr.name.?);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse failable with inferred `!` (new `-> T !` form)" {
|
test "parse single-value failable `-> (T, !)`" {
|
||||||
const source = "f :: () -> i32 ! { 0; }";
|
const source = "f :: () -> (i32, !) { 0; }";
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), source);
|
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);
|
try std.testing.expect(fields[1].data.error_type_expr.name == null);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "parse failable with named `!Foo` (new `-> Tuple(...) !` form)" {
|
test "parse multi-value named failable `-> (A, B, !Foo)`" {
|
||||||
const source = "f :: () -> Tuple(i32, i64) !ParseErr { 0; }";
|
const source = "f :: () -> (i32, i64, !ParseErr) { 0; }";
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), source);
|
var parser = Parser.init(arena.allocator(), source);
|
||||||
const root = try parser.parse();
|
const root = try parser.parse();
|
||||||
const rt = root.data.root.decls[0].data.fn_decl.return_type.?;
|
const rt = root.data.root.decls[0].data.fn_decl.return_type.?;
|
||||||
try std.testing.expect(rt.data == .tuple_type_expr);
|
try std.testing.expect(rt.data == .return_type_expr or rt.data == .tuple_type_expr);
|
||||||
const fields = rt.data.tuple_type_expr.field_types;
|
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.expectEqual(@as(usize, 3), fields.len);
|
||||||
try std.testing.expect(fields[2].data == .error_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.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" {
|
test "parse old bare-paren failable `-> (!, i32)` is rejected" {
|
||||||
const source = "f :: () -> (!, i32) { 0; }";
|
const source = "f :: () -> (!, i32) { 0; }";
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
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);
|
try std.testing.expectEqualStrings(source, aw.writer.toArrayList().items);
|
||||||
}
|
}
|
||||||
|
|
||||||
test "print: failable result list with pointer + named error folds to tuple repr" {
|
test "print: failable result list with pointer + named error renders canonically" {
|
||||||
// New `-> T !` form: a single value + named error channel folds to the SAME
|
// A single value + named error channel `(*Handle, !IoErr)` renders back as
|
||||||
// internal `tuple_type_expr` the old `(*Handle, !IoErr)` spelling produced,
|
// the canonical parenthesized result list.
|
||||||
// so printType still renders the canonical tuple representation.
|
const source = "open :: () -> (*Handle, !IoErr) { 0; }";
|
||||||
const source = "open :: () -> *Handle !IoErr { 0; }";
|
|
||||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||||
defer arena.deinit();
|
defer arena.deinit();
|
||||||
var parser = Parser.init(arena.allocator(), source);
|
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)" {
|
test "E0.3 full failable function parses end-to-end (all E0 forms)" {
|
||||||
const source =
|
const source =
|
||||||
\\parse :: (s: string) -> i32 !ParseErr {
|
\\parse :: (s: string) -> (i32, !ParseErr) {
|
||||||
\\ onfail (e) { cleanup(s); }
|
\\ onfail (e) { cleanup(s); }
|
||||||
\\ v := try inner(s) or 0;
|
\\ v := try inner(s) or 0;
|
||||||
\\ w := other(s) catch (e2) { return 0; };
|
\\ w := other(s) catch (e2) { return 0; };
|
||||||
|
|||||||
Reference in New Issue
Block a user