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; }
|
||||
|
||||
// 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; }
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
E :: error { Bad }
|
||||
|
||||
probe :: () -> i32 !E { return 21; }
|
||||
probe :: () -> (i32, !E) { return 21; }
|
||||
failing :: () -> !E { raise error.Bad; }
|
||||
|
||||
run :: () {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
E :: error { Bad }
|
||||
|
||||
failing :: () -> !E { raise error.Bad; }
|
||||
recover :: () -> i32 !E { return 21; }
|
||||
recover :: () -> (i32, !E) { return 21; }
|
||||
|
||||
work :: () {
|
||||
defer {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
E :: error { Boom }
|
||||
|
||||
f :: (fail: bool) -> i64 !E {
|
||||
f :: (fail: bool) -> (i64, !E) {
|
||||
if fail { raise error.Boom; }
|
||||
return 42;
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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.{};
|
||||
|
||||
20
library/vendors/sqlite/sqlite.sx
vendored
20
library/vendors/sqlite/sqlite.sx
vendored
@@ -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 };
|
||||
|
||||
@@ -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
|
||||
|
||||
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
|
||||
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(<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
|
||||
every closure flowing into any matching slot.
|
||||
- **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
|
||||
/// (`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
|
||||
|
||||
@@ -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
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
/// 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; };
|
||||
|
||||
Reference in New Issue
Block a user