try foo() catch (e) { } // legal
try foo() catch e { } // parse error with a migration hint
Same capture style as the for-loop. All four catch shapes keep working
with the parenthesized binding — block, bare-expression body, and the
== match sugar — and the no-binding forms are unchanged. onfail follows
the same rule (onfail (e) { }); its expression-cleanup form is
disambiguated by the paren-group-before-brace lookahead, so
onfail (f()); stays an expression cleanup.
AST unchanged; the printer renders the parens; the #run escape help
text updated. Corpus migrated (57 catch + 3 onfail bindings, in-source
parser test strings, specs incl. grammar rules, readme untouched —
no catch examples there).
Regression: examples/1157-diagnostics-catch-binding-needs-parens.sx;
re-captured stderr for 1010/1013/1037/1123 (migrated source echoed in
carets + help text).
A `v, err := failable()` destructure now binds the value slot(s) "live
only where `err` is proven absent". Reading `v` where the compiler cannot
prove `err == null` is a compile error.
New diagnostic-only Pass 1e (`checkErrorFlow` in ir/lower.zig): a
structured, path-sensitive walk over each main-file function body. A
proven-null set is threaded across branches and joined by intersection
at each `if`'s merge. Proof shapes recognized:
- `if !err { … v … }` (proven inside the guard)
- `if err { return/raise } … v` (proven on the fall-through)
- `if err { … } else { … v … }` (proven in the else branch)
- `!err and <reads v>` (short-circuit refinement)
Error-set tag compares (`if err == error.X`) prove nothing about
absence — they narrow the tag only. Nested lambdas are analyzed as their
own boundaries. Library modules are trusted (skipped).
Migrated the canon value-failable examples (1011/1012/1018/1044) to read
their value slots under `if !err` guards — output unchanged. New
regressions: 1046 (every proof shape compiles + runs, exit 210) and 1047
(unproven reads rejected, exit 1).
Gates: zig build, zig build test, run_examples.sh -> 338 passed, 0 failed.
Generic value-carrying failable composition works with the documented
$T: Type generic form (catch / destructure / failure-propagation / a
second monomorphization at a different T). Issue 0062 was an invalid-repro
report — it used the non-generic T: type form, which is a plain Type-valued
param, not a generic type parameter. Marked 0062 resolved (not a bug).
The only real residual: a non-$ T: Type function param used as a type
silently resolves to an empty {} (renders T{}) instead of erroring. Filed
as 0064 (deferred, orthogonal to ERR — the $T idiom works).
Regression: 1044-errors-generic-failable-composition.sx.