lang: catch/onfail error bindings take parens
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).
This commit is contained in:
38
specs.md
38
specs.md
@@ -109,7 +109,7 @@ M :: union { `s1: s32; } // union tag
|
||||
`u8, rest := pair(); // destructure name
|
||||
if `s16 := maybe() { } // optional binding
|
||||
for xs, 0.. (`bool, `u16) { } // for captures
|
||||
x catch `s2 { } // catch tag binding
|
||||
x catch (`s2) { } // catch tag binding
|
||||
```
|
||||
|
||||
In the **member-name positions** among these — struct field, union tag, and
|
||||
@@ -2819,7 +2819,7 @@ Statement form. Terminates the immediately enclosing failable function (like
|
||||
```sx
|
||||
if bad raise error.BadDigit; // literal tag
|
||||
|
||||
v := foo() catch e {
|
||||
v := foo() catch (e) {
|
||||
if e == error.Specific return default;
|
||||
raise e; // variable tag — re-raise
|
||||
};
|
||||
@@ -2854,37 +2854,39 @@ tag — use `catch` for that.
|
||||
|
||||
### `catch`
|
||||
|
||||
Expression form. Handles the error inline. The binding is a **bare name, no
|
||||
parens** (`catch e`), and is **optional**. Four shapes, disambiguated by the
|
||||
token after `catch`:
|
||||
Expression form. Handles the error inline. The binding is **parenthesized**
|
||||
(`catch (e)`) — like a for-loop capture — and is **optional**. Four shapes,
|
||||
disambiguated by the token after `catch`:
|
||||
|
||||
| Form | Binding | Body |
|
||||
|---|---|---|
|
||||
| `catch { ... }` | none (tag ignored) | block — braces required |
|
||||
| `catch e { ... }` | `e` | block |
|
||||
| `catch e EXPR` | `e` | bare expression (no braces) |
|
||||
| `catch e == { case ... }` | `e` | match over `e` (sugar for `{ if e == { ... } }`) |
|
||||
| `catch (e) { ... }` | `e` | block |
|
||||
| `catch (e) EXPR` | `e` | bare expression (no braces) |
|
||||
| `catch (e) == { case ... }` | `e` | match over `e` (sugar for `{ if e == { ... } }`) |
|
||||
|
||||
A bare binding (`catch (e) { }`) is a parse error with a migration hint.
|
||||
|
||||
```sx
|
||||
v := parse_digit(s) catch e {
|
||||
v := parse_digit(s) catch (e) {
|
||||
log.warn("bad input: {}", e);
|
||||
return default; // noreturn body
|
||||
};
|
||||
|
||||
v := parse_digit(s) catch e compute_fallback(e); // value-producing body
|
||||
v := parse_digit(s) catch (e) compute_fallback(e); // value-producing body
|
||||
|
||||
v, n := parse(s) catch e {
|
||||
v, n := parse(s) catch (e) {
|
||||
log.warn("parse failed: {}", e);
|
||||
(0, 0) // tuple body for a multi-value failable
|
||||
};
|
||||
|
||||
v := parse(s) catch e == { // match-body form
|
||||
v := parse(s) catch (e) == { // match-body form
|
||||
case .Empty: 0;
|
||||
case .BadDigit: -1;
|
||||
else: raise e;
|
||||
};
|
||||
|
||||
v := (try foo() or try boo()) catch e { return 0; }; // catch over an `or` chain
|
||||
v := (try foo() or try boo()) catch (e) { return 0; }; // catch over an `or` chain
|
||||
```
|
||||
|
||||
**Body type rule.** The body (block-as-expression) must produce the failable's
|
||||
@@ -2928,7 +2930,7 @@ of the other markers directly on `X`) is required.
|
||||
|
||||
```sx
|
||||
a := parse(s) or 0; // OK — terminator on the path
|
||||
a := parse(s) catch e {...}; // OK — catch marks
|
||||
a := parse(s) catch (e) {...}; // OK — catch marks
|
||||
v, err := failable(); // OK — destructure marks
|
||||
a := try foo() or try boo(); // OK — each try marks its own exit
|
||||
|
||||
@@ -2972,7 +2974,7 @@ Dropping the error slot is a compile error:
|
||||
v, _ := failable(); // ERROR: the error slot cannot be dropped — handle it
|
||||
```
|
||||
|
||||
Value slots may be discarded (`_, n := parse(s) catch e { return; }`). The
|
||||
Value slots may be discarded (`_, n := parse(s) catch (e) { return; }`). The
|
||||
statement form `try foo();` is the explicit "propagate, use no value." On a
|
||||
value-carrying failable, the value slot is live only where the compiler can
|
||||
prove the error slot is null (path-sensitive flow-check).
|
||||
@@ -2995,7 +2997,7 @@ make_handle :: () -> (Handle, !) {
|
||||
|
||||
open :: (path: string) -> (Handle, !) {
|
||||
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); }
|
||||
...
|
||||
}
|
||||
```
|
||||
@@ -3089,7 +3091,7 @@ return_stmt = 'return' expr? ';'
|
||||
break_stmt = 'break' ';'
|
||||
continue_stmt = 'continue' ';'
|
||||
raise_stmt = 'raise' expr ';'
|
||||
onfail_stmt = 'onfail' IDENT? block
|
||||
onfail_stmt = 'onfail' ('(' IDENT ')')? (block | expr ';')
|
||||
defer_stmt = 'defer' expr ';'
|
||||
insert_stmt = '#insert' expr ';'
|
||||
push_stmt = 'push' expr block
|
||||
@@ -3103,7 +3105,7 @@ for_iter = expr [range_op [expr]]
|
||||
range_op = '..' | '..=' | '..<' | '<..' | '<..=' | '<..<' | '=..' | '=..=' | '=..<'
|
||||
for_capture = '(' ['*'] IDENT (',' ['*'] IDENT)* ')'
|
||||
binary = catch_expr (binop catch_expr)* // binop includes `or` (fallback / chain)
|
||||
catch_expr = unary ('catch' IDENT? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
||||
catch_expr = unary ('catch' ('(' IDENT ')')? (block | '==' '{' case_arm* else_arm? '}' | unary))?
|
||||
unary = ('-' | '!' | 'xx' | 'try' | 'cast' '(' type ')') postfix
|
||||
| postfix
|
||||
postfix = primary ('(' args? ')' | '.' IDENT | '.{' field_init_list '}')*
|
||||
|
||||
Reference in New Issue
Block a user