errdefer and try/defer fix
This commit is contained in:
+1
-1
@@ -76,7 +76,7 @@ fields. `_` is not a keyword member name.
|
|||||||
- `while` loops with optional post-iteration update clauses
|
- `while` loops with optional post-iteration update clauses
|
||||||
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures
|
- `for` loops over ranges, arrays, slices, and pointers-to-arrays with copy captures, pointer captures `|@item|`, and optional `usize` index captures
|
||||||
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks
|
- `break`, `continue`, labeled `break :label`, labeled `continue :label`, and labeled plain blocks
|
||||||
- bare block scopes and `defer`, including LIFO flushing on fall-through, `return`, `break`, and `continue`
|
- bare block scopes, `defer`, and fallible-function `errdefer` with optional error capture; cleanup is block-scoped and LIFO
|
||||||
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `yield :label value`
|
- value blocks, value `if`, value loops, value `match`, `yield`, and labeled `yield :label value`
|
||||||
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
|
- `match` statements/expressions over enums, tagged unions, and scalars, including exhaustiveness checks, payload captures, pointer payload captures, multi-pattern arms, and scalar range patterns
|
||||||
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ Current prototype features:
|
|||||||
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by explicit or uniquely inferred leading comptime arguments
|
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by explicit or uniquely inferred leading comptime arguments
|
||||||
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
|
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
|
||||||
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
|
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
|
||||||
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`/`errdefer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
|
||||||
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`)
|
- Native function pointer values and types (`*func(...) R`, `*func(...) R ! E`, `?*func(...) R`)
|
||||||
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
|
- Typed allocation/reallocation through `std/mem` and generic dynamic arrays through `std/arraylist`
|
||||||
- Bodyless concrete C function declarations with exact external symbol names
|
- Bodyless concrete C function declarations with exact external symbol names
|
||||||
|
|||||||
@@ -300,6 +300,9 @@
|
|||||||
- `defer <stmt>` runs the statement when the enclosing block scope exits, in reverse
|
- `defer <stmt>` runs the statement when the enclosing block scope exits, in reverse
|
||||||
(LIFO) order, on every exit path: fall-through, `return`, `break`, `continue`. The
|
(LIFO) order, on every exit path: fall-through, `return`, `break`, `continue`. The
|
||||||
deferred statement may be a block (`defer { ... }`)
|
deferred statement may be a block (`defer { ... }`)
|
||||||
|
- `errdefer [|error|] <stmt>` is the fallible-function counterpart: it runs only when
|
||||||
|
an explicit or `try`-propagated error exits its active block scope, can capture the
|
||||||
|
widened enclosing error, and stays interleaved with ordinary defers in LIFO order
|
||||||
- bare block statements `{ ... }` were added as the enabling feature: a `{ ... }`
|
- bare block statements `{ ... }` were added as the enabling feature: a `{ ... }`
|
||||||
introduces a nested scope (locals are name-scoped to it; defers inside it fire at the
|
introduces a nested scope (locals are name-scoped to it; defers inside it fire at the
|
||||||
closing brace). A leading `{` is unambiguous since struct literals are postfix only
|
closing brace). A leading `{` is unambiguous since struct literals are postfix only
|
||||||
@@ -310,10 +313,8 @@
|
|||||||
the innermost loop body; fall-through flushes the current block's own defers. Deferring
|
the innermost loop body; fall-through flushes the current block's own defers. Deferring
|
||||||
a `return`/`break`/`continue`/`defer`, a `return` inside a `defer`, or a `break`/
|
a `return`/`break`/`continue`/`defer`, a `return` inside a `defer`, or a `break`/
|
||||||
`continue` that would escape a `defer` are all rejected
|
`continue` that would escape a `defer` are all rejected
|
||||||
- implemented entirely in lexer/parser/checker (new `Keyword_Defer`; `Block`/`Defer` AST
|
- cleanup is statically expanded with no runtime registration stack; `try` carries its
|
||||||
kinds reusing `body`/`update`; `parse_block_statement`/`parse_defer`). No HIR opcode:
|
active error-exit cleanup into lowering so propagation cannot bypass either defer form
|
||||||
a bare block is built and spliced inline, and a deferred statement is built once at the
|
|
||||||
`defer` site and its hir replayed at each exit, so lowering/codegen are unchanged
|
|
||||||
|
|
||||||
20. add `yield` statement (implemented; first pass — value blocks only; see below)
|
20. add `yield` statement (implemented; first pass — value blocks only; see below)
|
||||||
- a `{ ... }` on the right of a declaration or assignment is a *value block*: its final
|
- a `{ ... }` on the right of a declaration or assignment is a *value block*: its final
|
||||||
@@ -823,6 +824,10 @@
|
|||||||
conflicting targets
|
conflicting targets
|
||||||
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
- root `std` re-exports only `ArrayList(T)` for now; operations remain under `std/arraylist`
|
||||||
|
|
||||||
|
35. syntax highlighting (tree-sitter) updates (implemented)
|
||||||
|
- pointer sigils are highlighted as operators
|
||||||
|
- type-factory calls in type positions and struct literals are highlighted as functions
|
||||||
|
|
||||||
## A word on unchecked casts
|
## A word on unchecked casts
|
||||||
|
|
||||||
For casts that bypass safety checks, Honey provides builtin functions:
|
For casts that bypass safety checks, Honey provides builtin functions:
|
||||||
|
|||||||
@@ -169,6 +169,7 @@ Stmt :: struct {
|
|||||||
immutable: bool,
|
immutable: bool,
|
||||||
value_control_flow: bool,
|
value_control_flow: bool,
|
||||||
pointer_capture: bool,
|
pointer_capture: bool,
|
||||||
|
error_only: bool,
|
||||||
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
||||||
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
|
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
|
||||||
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
|
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
|
||||||
@@ -187,7 +188,8 @@ Stmt :: struct {
|
|||||||
// distinguish `|@item|` from copy capture.
|
// distinguish `|@item|` from copy capture.
|
||||||
// `Block` statements (a bare `{ ... }` scope) use `body` as their statements.
|
// `Block` statements (a bare `{ ... }` scope) use `body` as their statements.
|
||||||
// `Defer` statements use `update` as the deferred statement (which may itself
|
// `Defer` statements use `update` as the deferred statement (which may itself
|
||||||
// be a `Block`).
|
// be a `Block`), `error_only` for `errdefer`, and `captures` for its optional
|
||||||
|
// error capture.
|
||||||
// `Match` statements use `expr` as the subject and `body` as the list of arm
|
// `Match` statements use `expr` as the subject and `body` as the list of arm
|
||||||
// statements (each a `Match_Arm`). A `Match_Arm` uses `patterns` as its pattern
|
// statements (each a `Match_Arm`). A `Match_Arm` uses `patterns` as its pattern
|
||||||
// list (empty marks the `else` arm; more than one is a multi-pattern arm),
|
// list (empty marks the `else` arm; more than one is a multi-pattern arm),
|
||||||
|
|||||||
+121
-21
@@ -72,6 +72,12 @@ Yield_Target :: struct {
|
|||||||
defer_floor: int,
|
defer_floor: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Defer_Entry :: struct {
|
||||||
|
body: []hir.Stmt_Id,
|
||||||
|
error_only: bool,
|
||||||
|
capture: hir.Local_Id,
|
||||||
|
}
|
||||||
|
|
||||||
Build_Ctx :: struct {
|
Build_Ctx :: struct {
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
@@ -96,7 +102,7 @@ Build_Ctx :: struct {
|
|||||||
// to be valid). `defer_depth`/`loop_floor` guard control flow inside a deferred
|
// to be valid). `defer_depth`/`loop_floor` guard control flow inside a deferred
|
||||||
// statement: `return` is rejected while `defer_depth > 0`, and `break`/`continue`
|
// statement: `return` is rejected while `defer_depth > 0`, and `break`/`continue`
|
||||||
// only see loops opened within the defer (those past `loop_floor`).
|
// only see loops opened within the defer (those past `loop_floor`).
|
||||||
defers: ^[dynamic][]hir.Stmt_Id,
|
defers: ^[dynamic]Defer_Entry,
|
||||||
loop_defer_starts: ^[dynamic]int,
|
loop_defer_starts: ^[dynamic]int,
|
||||||
// Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID
|
// Parallel to `loop_defer_starts`: the label of each enclosing break target (INVALID
|
||||||
// when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled
|
// when unlabeled), so a `break :L` / `continue :L` can target an outer one. A labeled
|
||||||
@@ -3686,8 +3692,19 @@ infer_statements :: proc(
|
|||||||
case .Block:
|
case .Block:
|
||||||
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, statement.body, locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
case .Defer:
|
case .Defer:
|
||||||
|
capture_start := len(locals^)
|
||||||
|
if statement.error_only && len(statement.captures) > 0 &&
|
||||||
|
statement.captures[0] != checker.sink_symbol {
|
||||||
|
append(locals, Infer_Local{
|
||||||
|
name=statement.captures[0],
|
||||||
|
type=types.fallible_error(result^, &checker.module.types),
|
||||||
|
declared=types.fallible_error(result^, &checker.module.types),
|
||||||
|
statement=ast.INVALID_STMT,
|
||||||
|
})
|
||||||
|
}
|
||||||
deferred := [1]ast.Stmt_Id{statement.update}
|
deferred := [1]ast.Stmt_Id{statement.update}
|
||||||
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
infer_statements(checker, deferred[:], locals, local_types, pkg, file, demanded, result, result_hint)
|
||||||
|
resize(locals, capture_start)
|
||||||
case .Match:
|
case .Match:
|
||||||
// The build pass desugars `match` to an if/else chain, but inference runs first
|
// The build pass desugars `match` to an if/else chain, but inference runs first
|
||||||
// and must still visit the subject and arm bodies so calls there get specialized
|
// and must still visit the subject and arm bodies so calls there get specialized
|
||||||
@@ -5508,6 +5525,10 @@ build_compound_expr :: proc(
|
|||||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
case .Try:
|
case .Try:
|
||||||
|
if checker.current_build_ctx != nil && checker.current_build_ctx.defer_depth > 0 {
|
||||||
|
id := source.add(checker.diagnostics, expr.span, "cannot 'try' inside a 'defer'")
|
||||||
|
return invalid_hir_expr(checker, expr.span, id)
|
||||||
|
}
|
||||||
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
left_expected := expected if checker.ast_module.exprs[expr.left].kind == .Call else types.INVALID
|
||||||
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
channel := build_nested_expr(checker, expr.left, locals, global_reads, calls, left_expected, pkg, file)
|
||||||
channel_type := checker.module.exprs[channel].type
|
channel_type := checker.module.exprs[channel].type
|
||||||
@@ -5532,11 +5553,18 @@ build_compound_expr :: proc(
|
|||||||
id := source.add(checker.diagnostics, expr.span, "'try' error channel cannot be widened to the enclosing error channel")
|
id := source.add(checker.diagnostics, expr.span, "'try' error channel cannot be widened to the enclosing error channel")
|
||||||
return invalid_hir_expr(checker, expr.span, id, success)
|
return invalid_hir_expr(checker, expr.span, id, success)
|
||||||
}
|
}
|
||||||
|
cleanup: []hir.Stmt_Id
|
||||||
|
captures: []hir.Expr_Id
|
||||||
|
if checker.current_build_ctx != nil {
|
||||||
|
cleanup, captures = try_cleanup(checker.current_build_ctx)
|
||||||
|
}
|
||||||
return add_hir_expr(checker, hir.Expr{
|
return add_hir_expr(checker, hir.Expr{
|
||||||
kind=.Try,
|
kind=.Try,
|
||||||
span=expr.span,
|
span=expr.span,
|
||||||
type=success,
|
type=success,
|
||||||
left=channel,
|
left=channel,
|
||||||
|
body=cleanup,
|
||||||
|
args=captures,
|
||||||
target=hir.INVALID_REF,
|
target=hir.INVALID_REF,
|
||||||
right=hir.INVALID_EXPR,
|
right=hir.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -6701,17 +6729,51 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
|||||||
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
return fmt.aprintf("%s", strings.to_string(builder), allocator = checker.allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replay the deferred statements in frames `[lo, len(defers))` into `body`,
|
// Replay eligible cleanup in `[lo, len(defers))` in LIFO order. Error exits run
|
||||||
// innermost-most-recent first (LIFO across frames); each frame's own statements
|
// both defer forms; other exits skip errdefer. A direct error return supplies its
|
||||||
// keep their forward order. Used at every scope-exit path in `build_block`.
|
// preserved payload so captured errors can be initialized before each cleanup.
|
||||||
flush_defers :: proc(ctx: ^Build_Ctx, body: ^[dynamic]hir.Stmt_Id, lo: int) {
|
flush_defers :: proc(
|
||||||
|
ctx: ^Build_Ctx,
|
||||||
|
body: ^[dynamic]hir.Stmt_Id,
|
||||||
|
lo: int,
|
||||||
|
error_exit := false,
|
||||||
|
error_value := hir.INVALID_EXPR,
|
||||||
|
) {
|
||||||
for i := len(ctx.defers^) - 1; i >= lo; i -= 1 {
|
for i := len(ctx.defers^) - 1; i >= lo; i -= 1 {
|
||||||
for stmt_id in ctx.defers^[i] {
|
entry := ctx.defers^[i]
|
||||||
|
if entry.error_only && !error_exit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if error_exit && entry.capture != hir.INVALID_LOCAL && error_value != hir.INVALID_EXPR {
|
||||||
|
append(body, hir.stmt_id(len(ctx.checker.module.statements)))
|
||||||
|
append(&ctx.checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Declaration, local=entry.capture, expr=error_value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for stmt_id in entry.body {
|
||||||
append(body, stmt_id)
|
append(body, stmt_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy the active error-exit cleanup onto a Try expression. Capture locals are
|
||||||
|
// initialized by lowering once the propagated error has been extracted/widened.
|
||||||
|
try_cleanup :: proc(ctx: ^Build_Ctx) -> ([]hir.Stmt_Id, []hir.Expr_Id) {
|
||||||
|
body: [dynamic]hir.Stmt_Id
|
||||||
|
body.allocator = ctx.checker.allocator
|
||||||
|
captures: [dynamic]hir.Expr_Id
|
||||||
|
captures.allocator = ctx.checker.allocator
|
||||||
|
for i := len(ctx.defers^) - 1; i >= 0; i -= 1 {
|
||||||
|
entry := ctx.defers^[i]
|
||||||
|
if entry.error_only && entry.capture != hir.INVALID_LOCAL {
|
||||||
|
append(&captures, hir.Expr_Id(entry.capture))
|
||||||
|
}
|
||||||
|
append(&body, ..entry.body)
|
||||||
|
}
|
||||||
|
return body[:], captures[:]
|
||||||
|
}
|
||||||
|
|
||||||
build_block :: proc(
|
build_block :: proc(
|
||||||
ctx: ^Build_Ctx,
|
ctx: ^Build_Ctx,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
@@ -7144,6 +7206,8 @@ build_block :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
value := hir.INVALID_EXPR
|
value := hir.INVALID_EXPR
|
||||||
|
error_exit := false
|
||||||
|
error_value := hir.INVALID_EXPR
|
||||||
if statement.value_control_flow {
|
if statement.value_control_flow {
|
||||||
if types.kind(ctx.result, store) == .Fallible {
|
if types.kind(ctx.result, store) == .Fallible {
|
||||||
success := types.fallible_success(ctx.result, store)
|
success := types.fallible_success(ctx.result, store)
|
||||||
@@ -7157,13 +7221,12 @@ build_block :: proc(
|
|||||||
} else if types.kind(ctx.result, store) == .Fallible {
|
} else if types.kind(ctx.result, store) == .Fallible {
|
||||||
success := types.fallible_success(ctx.result, store)
|
success := types.fallible_success(ctx.result, store)
|
||||||
error_type := types.fallible_error(ctx.result, store)
|
error_type := types.fallible_error(ctx.result, store)
|
||||||
error_path := false
|
|
||||||
expr_ast := checker.ast_module.exprs[statement.expr]
|
expr_ast := checker.ast_module.exprs[statement.expr]
|
||||||
if expr_ast.kind == .Enum_Literal {
|
if expr_ast.kind == .Enum_Literal {
|
||||||
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
|
success_has := types.sum_has_name(store, success, u32(expr_ast.name))
|
||||||
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
|
error_has := types.sum_has_name(store, error_type, u32(expr_ast.name))
|
||||||
if error_has && !success_has {
|
if error_has && !success_has {
|
||||||
error_path = true
|
error_exit = true
|
||||||
} else if error_has && success_has {
|
} else if error_has && success_has {
|
||||||
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
|
id := source.add(checker.diagnostics, expr_ast.span, "ambiguous fallible return member")
|
||||||
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
|
value = invalid_hir_expr(checker, expr_ast.span, id, ctx.result)
|
||||||
@@ -7172,7 +7235,7 @@ build_block :: proc(
|
|||||||
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
|
target_pkg, available := expr_package(checker, expr_ast, ctx.pkg, ctx.file, true)
|
||||||
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name), file=u32(expr_lookup_file(expr_ast, ctx.file))) if available else types.INVALID
|
named := types.find_named(store, u32(target_pkg), u32(expr_ast.name), file=u32(expr_lookup_file(expr_ast, ctx.file))) if available else types.INVALID
|
||||||
named = types.resolve_alias(named, store)
|
named = types.resolve_alias(named, store)
|
||||||
error_path = can_implicitly_convert_type(checker, named, error_type)
|
error_exit = can_implicitly_convert_type(checker, named, error_type)
|
||||||
} else {
|
} else {
|
||||||
probe := build_expr(
|
probe := build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -7181,22 +7244,38 @@ build_block :: proc(
|
|||||||
probe_type := checker.module.exprs[probe].type
|
probe_type := checker.module.exprs[probe].type
|
||||||
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
if can_implicitly_convert_type(checker, probe_type, error_type) &&
|
||||||
!can_implicitly_convert_type(checker, probe_type, success) {
|
!can_implicitly_convert_type(checker, probe_type, success) {
|
||||||
error_path = true
|
error_exit = true
|
||||||
value = probe
|
value = probe
|
||||||
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
} else if can_implicitly_convert_type(checker, probe_type, success) {
|
||||||
value = probe
|
value = probe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value == hir.INVALID_EXPR {
|
if value == hir.INVALID_EXPR {
|
||||||
expected := error_type if error_path else success
|
expected := error_type if error_exit else success
|
||||||
value = build_expr(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
expected, ctx.pkg, ctx.file,
|
expected, ctx.pkg, ctx.file,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
expected := error_type if error_path else success
|
expected := error_type if error_exit else success
|
||||||
value = coerce_expr(checker, value, expected, statement.span)
|
value = coerce_expr(checker, value, expected, statement.span)
|
||||||
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_path)
|
if error_exit && len(ctx.defers^) > 0 && checker.module.exprs[value].kind != .Invalid {
|
||||||
|
tmp := append_tracked_local(
|
||||||
|
ctx.hir_locals, ctx.local_spans, ctx.local_used, ctx.local_warnable,
|
||||||
|
hir.Local{name=checker.sink_symbol, type=error_type, mutable=false}, source.Span{},
|
||||||
|
)
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Declaration, span=statement.span, local=tmp, expr=value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
error_value = hir.expr_id(len(checker.module.exprs))
|
||||||
|
append(&checker.module.exprs, hir.Expr{
|
||||||
|
kind=.Local, span=statement.span, type=error_type, target=hir.local_ref(tmp),
|
||||||
|
})
|
||||||
|
value = error_value
|
||||||
|
}
|
||||||
|
value = fallible_aggregate(checker, statement.span, ctx.result, value, error_exit)
|
||||||
} else {
|
} else {
|
||||||
value = build_expr(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -7210,7 +7289,7 @@ build_block :: proc(
|
|||||||
// can't change what is returned — Zig evaluates the return value, then
|
// can't change what is returned — Zig evaluates the return value, then
|
||||||
// runs defers.
|
// runs defers.
|
||||||
if len(ctx.defers^) > 0 {
|
if len(ctx.defers^) > 0 {
|
||||||
if checker.module.exprs[value].kind != .Invalid {
|
if !error_exit && checker.module.exprs[value].kind != .Invalid {
|
||||||
tmp := append_tracked_local(
|
tmp := append_tracked_local(
|
||||||
ctx.hir_locals,
|
ctx.hir_locals,
|
||||||
ctx.local_spans,
|
ctx.local_spans,
|
||||||
@@ -7229,7 +7308,7 @@ build_block :: proc(
|
|||||||
kind = .Local, span = statement.span, type = ctx.result, target = hir.local_ref(tmp),
|
kind = .Local, span = statement.span, type = ctx.result, target = hir.local_ref(tmp),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
flush_defers(ctx, &body, 0)
|
flush_defers(ctx, &body, 0, error_exit, error_value)
|
||||||
}
|
}
|
||||||
append(&body, hir.stmt_id(len(checker.module.statements)))
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
append(&checker.module.statements, hir.Stmt{
|
append(&checker.module.statements, hir.Stmt{
|
||||||
@@ -7753,16 +7832,37 @@ build_block :: proc(
|
|||||||
ctx.problematic^ = true
|
ctx.problematic^ = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if statement.error_only && types.kind(ctx.result, store) != .Fallible {
|
||||||
|
id := source.add(checker.diagnostics, statement.span, "'errdefer' requires an enclosing fallible function")
|
||||||
|
append(&body, hir.stmt_id(len(checker.module.statements)))
|
||||||
|
append(&checker.module.statements, hir.Stmt{
|
||||||
|
kind=.Trap, span=statement.span, expr=hir.INVALID_EXPR,
|
||||||
|
local=hir.INVALID_LOCAL, diagnostic=id,
|
||||||
|
})
|
||||||
|
ctx.problematic^ = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
// Build the deferred statement once, guarded so a `return` inside it is
|
// Build the deferred statement once, guarded so a `return` inside it is
|
||||||
// rejected and `break`/`continue` only target loops opened within the
|
// rejected and `break`/`continue` only target loops opened within the
|
||||||
// defer; its hir is replayed at each scope exit, not emitted here.
|
// defer; its hir is replayed at each scope exit, not emitted here.
|
||||||
|
capture_start := len(ctx.locals^)
|
||||||
|
capture := hir.INVALID_LOCAL
|
||||||
|
if statement.error_only && len(statement.captures) > 0 &&
|
||||||
|
statement.captures[0] != checker.sink_symbol {
|
||||||
|
capture = append_build_local(
|
||||||
|
ctx, statement.captures[0], types.fallible_error(ctx.result, store), false, statement.span,
|
||||||
|
)
|
||||||
|
}
|
||||||
saved_floor := ctx.loop_floor
|
saved_floor := ctx.loop_floor
|
||||||
ctx.defer_depth += 1
|
ctx.defer_depth += 1
|
||||||
ctx.loop_floor = len(ctx.loop_defer_starts^)
|
ctx.loop_floor = len(ctx.loop_defer_starts^)
|
||||||
entry := build_block(ctx, []ast.Stmt_Id{statement.update})
|
entry := build_block(ctx, []ast.Stmt_Id{statement.update})
|
||||||
ctx.loop_floor = saved_floor
|
ctx.loop_floor = saved_floor
|
||||||
ctx.defer_depth -= 1
|
ctx.defer_depth -= 1
|
||||||
append(ctx.defers, entry)
|
resize(ctx.locals, capture_start)
|
||||||
|
append(ctx.defers, Defer_Entry{
|
||||||
|
body=entry, error_only=statement.error_only, capture=capture,
|
||||||
|
})
|
||||||
case .Match:
|
case .Match:
|
||||||
build_match(ctx, &body, statement)
|
build_match(ctx, &body, statement)
|
||||||
case .Match_Arm:
|
case .Match_Arm:
|
||||||
@@ -7795,7 +7895,7 @@ build_block :: proc(
|
|||||||
// Free this block's deferred-statement entry slices (their stmt ids were
|
// Free this block's deferred-statement entry slices (their stmt ids were
|
||||||
// already replayed at every path that can leave this block) and pop the frame.
|
// already replayed at every path that can leave this block) and pop the frame.
|
||||||
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
delete(ctx.defers^[i], checker.allocator)
|
delete(ctx.defers^[i].body, checker.allocator)
|
||||||
}
|
}
|
||||||
resize(ctx.defers, defer_start)
|
resize(ctx.defers, defer_start)
|
||||||
resize(ctx.locals, scope_start)
|
resize(ctx.locals, scope_start)
|
||||||
@@ -7887,7 +7987,7 @@ build_value_block :: proc(
|
|||||||
}
|
}
|
||||||
// Close the scope (build_block left it open for us).
|
// Close the scope (build_block left it open for us).
|
||||||
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
delete(ctx.defers^[i], checker.allocator)
|
delete(ctx.defers^[i].body, checker.allocator)
|
||||||
}
|
}
|
||||||
resize(ctx.defers, defer_start)
|
resize(ctx.defers, defer_start)
|
||||||
resize(ctx.locals, scope_start)
|
resize(ctx.locals, scope_start)
|
||||||
@@ -8924,7 +9024,7 @@ block_element_type :: proc(ctx: ^Build_Ctx, block_stmts: []ast.Stmt_Id) -> types
|
|||||||
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
result := checker.module.exprs[probe].type if checker.module.exprs[probe].kind != .Invalid else types.INVALID
|
||||||
// Discard the throwaway leading build's scope (its hir stmts/locals are dead but stable).
|
// Discard the throwaway leading build's scope (its hir stmts/locals are dead but stable).
|
||||||
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
for i := defer_start; i < len(ctx.defers^); i += 1 {
|
||||||
delete(ctx.defers^[i], checker.allocator)
|
delete(ctx.defers^[i].body, checker.allocator)
|
||||||
}
|
}
|
||||||
resize(ctx.defers, defer_start)
|
resize(ctx.defers, defer_start)
|
||||||
resize(ctx.locals, scope_start)
|
resize(ctx.locals, scope_start)
|
||||||
@@ -9310,7 +9410,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
defers: [dynamic][]hir.Stmt_Id
|
defers: [dynamic]Defer_Entry
|
||||||
defers.allocator = checker.allocator
|
defers.allocator = checker.allocator
|
||||||
loop_defer_starts: [dynamic]int
|
loop_defer_starts: [dynamic]int
|
||||||
loop_defer_starts.allocator = checker.allocator
|
loop_defer_starts.allocator = checker.allocator
|
||||||
@@ -9392,7 +9492,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
for entry in defers {
|
for entry in defers {
|
||||||
delete(entry, checker.allocator)
|
delete(entry.body, checker.allocator)
|
||||||
}
|
}
|
||||||
delete(defers)
|
delete(defers)
|
||||||
delete(loop_defer_starts)
|
delete(loop_defer_starts)
|
||||||
|
|||||||
@@ -290,7 +290,8 @@ Ct_State :: struct {
|
|||||||
places: [dynamic]Ct_Place,
|
places: [dynamic]Ct_Place,
|
||||||
paths: [dynamic]Ct_Path_Elem,
|
paths: [dynamic]Ct_Path_Elem,
|
||||||
bindings: [dynamic]Ct_Binding,
|
bindings: [dynamic]Ct_Binding,
|
||||||
defers: [dynamic]ast.Stmt_Id,
|
defers: [dynamic]Ct_Defer,
|
||||||
|
defer_depth: int,
|
||||||
steps: int,
|
steps: int,
|
||||||
error: Ct_Error_Kind,
|
error: Ct_Error_Kind,
|
||||||
diagnostic: source.Diagnostic_Id,
|
diagnostic: source.Diagnostic_Id,
|
||||||
@@ -298,6 +299,12 @@ Ct_State :: struct {
|
|||||||
demanded: ^[dynamic]Spec_Id,
|
demanded: ^[dynamic]Spec_Id,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ct_Defer :: struct {
|
||||||
|
statement: ast.Stmt_Id,
|
||||||
|
error_only: bool,
|
||||||
|
capture: symbol.Id,
|
||||||
|
}
|
||||||
|
|
||||||
ct_state_make :: proc(
|
ct_state_make :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
pkg: ast.Package_Id,
|
pkg: ast.Package_Id,
|
||||||
@@ -2217,6 +2224,9 @@ ct_clone_value :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
|
||||||
|
if state.defer_depth > 0 {
|
||||||
|
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "cannot 'try' inside a 'defer'")
|
||||||
|
}
|
||||||
checker := state.checker
|
checker := state.checker
|
||||||
channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
channel, flow, ok := ct_eval_expr(state, expr.left, types.INVALID, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
@@ -2465,7 +2475,17 @@ ct_exec_statements :: proc(
|
|||||||
case .Block:
|
case .Block:
|
||||||
flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
flow, ok = ct_exec_statements(state, statement.body, yield_returns, depth+1)
|
||||||
case .Defer:
|
case .Defer:
|
||||||
append(&state.defers, statement.update)
|
if statement.error_only && types.kind(state.result, &checker.module.types) != .Fallible {
|
||||||
|
ok = ct_fail(state, .Not_Comptime, statement.span, "'errdefer' requires an enclosing fallible function")
|
||||||
|
} else {
|
||||||
|
capture := symbol.INVALID
|
||||||
|
if len(statement.captures) > 0 {
|
||||||
|
capture = statement.captures[0]
|
||||||
|
}
|
||||||
|
append(&state.defers, Ct_Defer{
|
||||||
|
statement=statement.update, error_only=statement.error_only, capture=capture,
|
||||||
|
})
|
||||||
|
}
|
||||||
case .Match:
|
case .Match:
|
||||||
flow, ok = ct_exec_match(state, statement, yield_returns, depth+1)
|
flow, ok = ct_exec_match(state, statement, yield_returns, depth+1)
|
||||||
case .Match_Arm:
|
case .Match_Arm:
|
||||||
@@ -2477,22 +2497,50 @@ ct_exec_statements :: proc(
|
|||||||
return flow, false
|
return flow, false
|
||||||
}
|
}
|
||||||
if flow.kind != .Normal {
|
if flow.kind != .Normal {
|
||||||
if !ct_flush_defers(state, defer_start, depth+1) {
|
if !ct_flush_defers(state, defer_start, flow, depth+1) {
|
||||||
return flow, false
|
return flow, false
|
||||||
}
|
}
|
||||||
return flow, true
|
return flow, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !ct_flush_defers(state, defer_start, depth+1) {
|
if !ct_flush_defers(state, defer_start, ct_flow(.Normal), depth+1) {
|
||||||
return ct_flow(.Normal), false
|
return ct_flow(.Normal), false
|
||||||
}
|
}
|
||||||
return ct_flow(.Normal), true
|
return ct_flow(.Normal), true
|
||||||
}
|
}
|
||||||
|
|
||||||
ct_flush_defers :: proc(state: ^Ct_State, start: int, depth: int) -> bool {
|
ct_flush_defers :: proc(state: ^Ct_State, start: int, exit: Ct_Flow, depth: int) -> bool {
|
||||||
|
error_exit := false
|
||||||
|
error_value := INVALID_CT_VALUE
|
||||||
|
if exit.kind == .Return && exit.value != INVALID_CT_VALUE && int(exit.value) < len(state.values) {
|
||||||
|
returned := state.values[exit.value]
|
||||||
|
if returned.kind == .Fallible && returned.active != 0 {
|
||||||
|
error_exit = true
|
||||||
|
children := ct_child_slice(state, returned)
|
||||||
|
if len(children) > 0 {
|
||||||
|
error_value = children[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for index := len(state.defers) - 1; index >= start; index -= 1 {
|
for index := len(state.defers) - 1; index >= start; index -= 1 {
|
||||||
stmt := [1]ast.Stmt_Id{state.defers[index]}
|
entry := state.defers[index]
|
||||||
|
if entry.error_only && !error_exit {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
binding_start := len(state.bindings)
|
||||||
|
bound_capture := false
|
||||||
|
if entry.error_only && symbol.is_valid(entry.capture) &&
|
||||||
|
entry.capture != state.checker.sink_symbol && error_value != INVALID_CT_VALUE {
|
||||||
|
ct_bind_value(state, entry.capture, state.values[error_value].type, error_value, false)
|
||||||
|
bound_capture = true
|
||||||
|
}
|
||||||
|
stmt := [1]ast.Stmt_Id{entry.statement}
|
||||||
|
state.defer_depth += 1
|
||||||
flow, ok := ct_exec_statements(state, stmt[:], false, depth+1)
|
flow, ok := ct_exec_statements(state, stmt[:], false, depth+1)
|
||||||
|
state.defer_depth -= 1
|
||||||
|
if bound_capture {
|
||||||
|
ct_pop_bindings(state, binding_start)
|
||||||
|
}
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,10 +136,13 @@ Expr :: struct {
|
|||||||
span: source.Span,
|
span: source.Span,
|
||||||
type: types.Type,
|
type: types.Type,
|
||||||
integer: i64,
|
integer: i64,
|
||||||
|
// Aggregate/call children normally; Try stores errdefer capture local IDs
|
||||||
|
// encoded as Expr_Id because it otherwise has no args.
|
||||||
args: []Expr_Id,
|
args: []Expr_Id,
|
||||||
// `Catch` block handlers use `body` for the handler statements and `target`
|
// `Catch` block handlers use `body` for the handler statements and `target`
|
||||||
// for the optional captured error local. A missing `right` means the handler
|
// for the optional captured error local. A missing `right` means the handler
|
||||||
// exits on every path and therefore has no fallback value.
|
// exits on every path and therefore has no fallback value. `Try` uses `body`
|
||||||
|
// for active error-exit cleanup.
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
target: Ref,
|
target: Ref,
|
||||||
left: Expr_Id,
|
left: Expr_Id,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "break": return .Keyword_Break
|
case "break": return .Keyword_Break
|
||||||
case "continue": return .Keyword_Continue
|
case "continue": return .Keyword_Continue
|
||||||
case "defer": return .Keyword_Defer
|
case "defer": return .Keyword_Defer
|
||||||
|
case "errdefer": return .Keyword_Errdefer
|
||||||
case "yield": return .Keyword_Yield
|
case "yield": return .Keyword_Yield
|
||||||
case "match": return .Keyword_Match
|
case "match": return .Keyword_Match
|
||||||
case "else": return .Keyword_Else
|
case "else": return .Keyword_Else
|
||||||
|
|||||||
@@ -416,10 +416,11 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
})
|
})
|
||||||
if expr.kind == .Try {
|
if expr.kind == .Try {
|
||||||
result := channel
|
result := channel
|
||||||
if !types.equal(channel_type, state.func_result) {
|
error_value := ir.INVALID_INSTRUCTION
|
||||||
|
if !types.equal(channel_type, state.func_result) || len(expr.args) > 0 {
|
||||||
error_type := types.fallible_error(channel_type, &state.hir_module.types)
|
error_type := types.fallible_error(channel_type, &state.hir_module.types)
|
||||||
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
|
enclosing_error := types.fallible_error(state.func_result, &state.hir_module.types)
|
||||||
error_value := append_instruction(state, ir.Instruction{
|
error_value = append_instruction(state, ir.Instruction{
|
||||||
op=.Fallible_Error, span=expr.span, type=error_type,
|
op=.Fallible_Error, span=expr.span, type=error_type,
|
||||||
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=channel_slot, b=ir.INVALID_INSTRUCTION,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -431,14 +432,36 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
|||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
args := make([]ir.Instruction_Id, 1, state.allocator)
|
if !types.equal(channel_type, state.func_result) {
|
||||||
args[0] = error_value
|
args := make([]ir.Instruction_Id, 1, state.allocator)
|
||||||
result = append_instruction(state, ir.Instruction{
|
args[0] = error_value
|
||||||
op=.Aggregate, span=expr.span, type=state.func_result, integer=1,
|
result = append_instruction(state, ir.Instruction{
|
||||||
args=args, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION,
|
op=.Aggregate, span=expr.span, type=state.func_result, integer=1,
|
||||||
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
args=args, target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION,
|
||||||
|
b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for encoded_capture in expr.args {
|
||||||
|
capture := hir.Local_Id(encoded_capture)
|
||||||
|
if capture == hir.INVALID_LOCAL || int(capture) >= len(state.func_locals) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
error_type := state.func_locals[capture].type
|
||||||
|
capture_slot := append_instruction(state, ir.Instruction{
|
||||||
|
op=.Alloca, span=expr.span, type=error_type,
|
||||||
|
target=ir.local_ref(ir.Local_Id(capture)),
|
||||||
|
a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
|
})
|
||||||
|
state.local_slots[capture] = capture_slot
|
||||||
|
append_instruction(state, ir.Instruction{
|
||||||
|
op=.Store, span=expr.span, type=error_type,
|
||||||
|
target=ir.INVALID_REF, a=capture_slot, b=error_value,
|
||||||
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
lower_statements(state, expr.body)
|
||||||
append_instruction(state, ir.Instruction{
|
append_instruction(state, ir.Instruction{
|
||||||
op=.Return, span=expr.span, type=state.func_result,
|
op=.Return, span=expr.span, type=state.func_result,
|
||||||
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
|
target=ir.INVALID_REF, a=result, b=ir.INVALID_INSTRUCTION,
|
||||||
|
|||||||
@@ -1410,18 +1410,44 @@ parse_block_statement :: proc(parser: ^Parser, label := symbol.INVALID) -> ast.S
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
// `defer <statement>` runs the statement when the enclosing scope exits. The
|
// `defer <statement>` runs whenever the enclosing scope exits; `errdefer`
|
||||||
// statement may be a block (`defer { ... }`). The checker rejects deferring a
|
// runs only when it exits through a function error and may capture that error.
|
||||||
// `return`/`break`/`continue`/`defer`.
|
|
||||||
parse_defer :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
parse_defer :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
||||||
marker := advance(parser) // consume 'defer'
|
marker := advance(parser)
|
||||||
|
error_only := marker.kind == .Keyword_Errdefer
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
captures: []symbol.Id
|
||||||
|
if error_only {
|
||||||
|
if _, ok := allow(parser, .Pipe); ok {
|
||||||
|
capture := current(parser)
|
||||||
|
if capture.kind != .Identifier && capture.kind != .Underscore {
|
||||||
|
source.add(parser.diagnostics, capture.span, "expected an errdefer capture name")
|
||||||
|
} else {
|
||||||
|
advance(parser)
|
||||||
|
captures = make([]symbol.Id, 1, parser.module.allocator)
|
||||||
|
captures[0] = capture.symbol
|
||||||
|
}
|
||||||
|
if current(parser).kind == .Comma {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "'errdefer' accepts exactly one capture")
|
||||||
|
for current(parser).kind != .Pipe && current(parser).kind != .Newline &&
|
||||||
|
current(parser).kind != .Eof {
|
||||||
|
advance(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, close_ok := allow(parser, .Pipe); !close_ok {
|
||||||
|
source.add(parser.diagnostics, current(parser).span, "expected '|' after errdefer capture")
|
||||||
|
}
|
||||||
|
skip_newlines(parser)
|
||||||
|
}
|
||||||
|
}
|
||||||
inner := parse_statement(parser)
|
inner := parse_statement(parser)
|
||||||
id := ast.stmt_id(len(parser.module.statements))
|
id := ast.stmt_id(len(parser.module.statements))
|
||||||
append(&parser.module.statements, ast.Stmt{
|
append(&parser.module.statements, ast.Stmt{
|
||||||
kind=.Defer,
|
kind=.Defer,
|
||||||
span=marker.span,
|
span=marker.span,
|
||||||
update=inner,
|
update=inner,
|
||||||
|
error_only=error_only,
|
||||||
|
captures=captures,
|
||||||
expr=ast.INVALID_EXPR,
|
expr=ast.INVALID_EXPR,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
@@ -1486,7 +1512,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
if current(parser).kind == .Keyword_Continue {
|
if current(parser).kind == .Keyword_Continue {
|
||||||
return parse_loop_control(parser, .Continue)
|
return parse_loop_control(parser, .Continue)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Keyword_Defer {
|
if current(parser).kind == .Keyword_Defer || current(parser).kind == .Keyword_Errdefer {
|
||||||
return parse_defer(parser)
|
return parse_defer(parser)
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Keyword_Yield {
|
if current(parser).kind == .Keyword_Yield {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Break,
|
Keyword_Break,
|
||||||
Keyword_Continue,
|
Keyword_Continue,
|
||||||
Keyword_Defer,
|
Keyword_Defer,
|
||||||
|
Keyword_Errdefer,
|
||||||
Keyword_Yield,
|
Keyword_Yield,
|
||||||
Keyword_Match,
|
Keyword_Match,
|
||||||
Keyword_Else,
|
Keyword_Else,
|
||||||
|
|||||||
@@ -3747,8 +3747,13 @@ defer_misuse_is_diagnosed :: proc(t: ^testing.T) {
|
|||||||
bad_defer_return()
|
bad_defer_return()
|
||||||
bad_defer_break()
|
bad_defer_break()
|
||||||
bad_return_in_defer()
|
bad_return_in_defer()
|
||||||
|
bad_errdefer_nonfallible()
|
||||||
|
_ = bad_try_in_defer() catch 0
|
||||||
|
_ = bad_try_in_errdefer() catch 0
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
Failure :: enum { bad }
|
||||||
|
fail func() i32 ! Failure { return .bad }
|
||||||
bad_defer_return func() void {
|
bad_defer_return func() void {
|
||||||
defer return
|
defer return
|
||||||
}
|
}
|
||||||
@@ -3763,6 +3768,17 @@ bad_return_in_defer func() void {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bad_errdefer_nonfallible func() void {
|
||||||
|
errdefer {}
|
||||||
|
}
|
||||||
|
bad_try_in_defer func() i32 ! Failure {
|
||||||
|
defer _ = try fail()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
bad_try_in_errdefer func() i32 ! Failure {
|
||||||
|
errdefer _ = try fail()
|
||||||
|
return 1
|
||||||
|
}
|
||||||
`
|
`
|
||||||
source_file := source.Source{path="test.bro", text=text}
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
diagnostics := source.init_diagnostics(&source_file)
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
@@ -3779,14 +3795,50 @@ bad_return_in_defer func() void {
|
|||||||
defer_return := false
|
defer_return := false
|
||||||
defer_break := false
|
defer_break := false
|
||||||
return_in_defer := false
|
return_in_defer := false
|
||||||
|
errdefer_nonfallible := false
|
||||||
|
try_in_defer := 0
|
||||||
for diagnostic in diagnostics.items {
|
for diagnostic in diagnostics.items {
|
||||||
defer_return = defer_return || strings.contains(diagnostic.message, "cannot defer a 'return' statement")
|
defer_return = defer_return || strings.contains(diagnostic.message, "cannot defer a 'return' statement")
|
||||||
defer_break = defer_break || strings.contains(diagnostic.message, "cannot defer a 'break' statement")
|
defer_break = defer_break || strings.contains(diagnostic.message, "cannot defer a 'break' statement")
|
||||||
return_in_defer = return_in_defer || strings.contains(diagnostic.message, "cannot 'return' inside a 'defer'")
|
return_in_defer = return_in_defer || strings.contains(diagnostic.message, "cannot 'return' inside a 'defer'")
|
||||||
|
errdefer_nonfallible = errdefer_nonfallible || strings.contains(diagnostic.message, "'errdefer' requires an enclosing fallible function")
|
||||||
|
try_in_defer += 1 if strings.contains(diagnostic.message, "cannot 'try' inside a 'defer'") else 0
|
||||||
}
|
}
|
||||||
testing.expect(t, defer_return)
|
testing.expect(t, defer_return)
|
||||||
testing.expect(t, defer_break)
|
testing.expect(t, defer_break)
|
||||||
testing.expect(t, return_in_defer)
|
testing.expect(t, return_in_defer)
|
||||||
|
testing.expect(t, errdefer_nonfallible)
|
||||||
|
testing.expect_value(t, try_in_defer, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
errdefer_capture_syntax_is_diagnosed :: proc(t: ^testing.T) {
|
||||||
|
text := `Failure :: enum { bad }
|
||||||
|
bad func() i32 ! Failure {
|
||||||
|
errdefer || {}
|
||||||
|
errdefer |first, second| {}
|
||||||
|
return .bad
|
||||||
|
}
|
||||||
|
main func() i32 { return bad() catch 0 }
|
||||||
|
`
|
||||||
|
source_file := source.Source{path="test.bro", text=text}
|
||||||
|
diagnostics := source.init_diagnostics(&source_file)
|
||||||
|
defer source.destroy_diagnostics(&diagnostics)
|
||||||
|
symbols := symbol.init_table()
|
||||||
|
defer symbol.destroy_table(&symbols)
|
||||||
|
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||||
|
defer delete(stream.items)
|
||||||
|
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||||
|
defer ast.destroy_module(&module)
|
||||||
|
|
||||||
|
missing := false
|
||||||
|
multiple := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
missing = missing || strings.contains(diagnostic.message, "expected an errdefer capture name")
|
||||||
|
multiple = multiple || strings.contains(diagnostic.message, "'errdefer' accepts exactly one capture")
|
||||||
|
}
|
||||||
|
testing.expect(t, missing)
|
||||||
|
testing.expect(t, multiple)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
|
|||||||
@@ -91,6 +91,36 @@ use_try func() i32 ! Error {
|
|||||||
return value + 1
|
return value + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ct_errdefer func(fail bool) i32 ! Error {
|
||||||
|
trace i32 = 0
|
||||||
|
defer trace = trace * 10 + 1
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) trace = trace * 10 + 2
|
||||||
|
}
|
||||||
|
defer trace = trace * 10 + 3
|
||||||
|
if (fail) return .bad
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_try_errdefer func() i32 ! Error {
|
||||||
|
trace i32 = 0
|
||||||
|
defer trace = trace * 10 + 4
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) trace = trace * 10 + 5
|
||||||
|
}
|
||||||
|
return try may_fail(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
ct_errdefer_check func() i32 {
|
||||||
|
ok i32 :: ct_errdefer(false) catch 0
|
||||||
|
if (ok != 7) return 1
|
||||||
|
explicit_error i32 :: ct_errdefer(true) catch 9
|
||||||
|
if (explicit_error != 9) return 2
|
||||||
|
try_error i32 :: ct_try_errdefer() catch 9
|
||||||
|
if (try_error != 9) return 3
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
||||||
recover func() i32 {
|
recover func() i32 {
|
||||||
return may_fail(true) catch |e| {
|
return may_fail(true) catch |e| {
|
||||||
match e {
|
match e {
|
||||||
@@ -135,6 +165,7 @@ storage_mutation func() i32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GLOBAL :: $sum_loop(4)
|
GLOBAL :: $sum_loop(4)
|
||||||
|
ERRDEFER :: $ct_errdefer_check()
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
point Point :: $make_point()
|
point Point :: $make_point()
|
||||||
@@ -205,5 +236,8 @@ main func() i32 {
|
|||||||
if fallible_err != 13 {
|
if fallible_err != 13 {
|
||||||
return 16
|
return 16
|
||||||
}
|
}
|
||||||
|
if ERRDEFER != 42 {
|
||||||
|
return 17
|
||||||
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Milestone 19: `defer` and bare block statements.
|
# Milestone 19: `defer`, `errdefer`, and bare block statements.
|
||||||
#
|
#
|
||||||
# `defer <stmt>` runs the statement when the enclosing scope exits, in reverse
|
# `defer <stmt>` runs the statement when the enclosing scope exits, in reverse
|
||||||
# (LIFO) order, on every exit path. A bare `{ ... }` introduces a scope. Each
|
# (LIFO) order, on every exit path. A bare `{ ... }` introduces a scope. Each
|
||||||
@@ -24,6 +24,66 @@ enclosing_defer_check func() i32 {
|
|||||||
return v # 0->1 (i=0 fall-through), ->2 (i=1 break); the +100 runs after capture
|
return v # 0->1 (i=0 fall-through), ->2 (i=1 break); the +100 runs after capture
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CleanupError :: enum {
|
||||||
|
bad
|
||||||
|
}
|
||||||
|
|
||||||
|
ExtraError :: enum {
|
||||||
|
extra
|
||||||
|
}
|
||||||
|
|
||||||
|
CleanupErrors :: alias CleanupError | ExtraError
|
||||||
|
|
||||||
|
explicit_cleanup func(fail bool, trace *mut i32) i32 ! CleanupError {
|
||||||
|
defer trace^ = trace^ * 10 + 1
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) {
|
||||||
|
trace^ = trace^ * 10 + 2
|
||||||
|
} else {
|
||||||
|
trace^ = 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer trace^ = trace^ * 10 + 3
|
||||||
|
if (fail) return .bad
|
||||||
|
return 7
|
||||||
|
}
|
||||||
|
|
||||||
|
fail_cleanup func() i32 ! CleanupError {
|
||||||
|
return .bad
|
||||||
|
}
|
||||||
|
|
||||||
|
try_cleanup func(trace *mut i32) i32 ! CleanupError {
|
||||||
|
defer trace^ = trace^ * 10 + 4
|
||||||
|
errdefer trace^ = trace^ * 10 + 5
|
||||||
|
return try fail_cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
widen_cleanup func(trace *mut i32) i32 ! CleanupErrors {
|
||||||
|
errdefer |err| {
|
||||||
|
match err {
|
||||||
|
.bad: trace^ = trace^ * 10 + 6
|
||||||
|
.extra: trace^ = 99
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return try fail_cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_cleanup func(trace *mut i32) i32 ! CleanupError {
|
||||||
|
errdefer trace^ = trace^ * 10 + 7
|
||||||
|
{
|
||||||
|
errdefer trace^ = 99
|
||||||
|
}
|
||||||
|
return .bad
|
||||||
|
}
|
||||||
|
|
||||||
|
multi_exit_cleanup func(direct bool, trace *mut i32) i32 ! CleanupError {
|
||||||
|
errdefer |err| {
|
||||||
|
if (err == .bad) trace^ = trace^ + 8
|
||||||
|
}
|
||||||
|
if (direct) return .bad
|
||||||
|
return try fail_cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
main func() i32 {
|
main func() i32 {
|
||||||
# 1. return value captured before defers run.
|
# 1. return value captured before defers run.
|
||||||
if (spill_check() != 5) return 101
|
if (spill_check() != 5) return 101
|
||||||
@@ -78,5 +138,31 @@ main func() i32 {
|
|||||||
# 7. break does not run an enclosing function-scope defer.
|
# 7. break does not run an enclosing function-scope defer.
|
||||||
if (enclosing_defer_check() != 2) return 107
|
if (enclosing_defer_check() != 2) return 107
|
||||||
|
|
||||||
|
# 8. errdefer is skipped on success; ordinary defers stay interleaved.
|
||||||
|
trace i32 = 0
|
||||||
|
if ((explicit_cleanup(false, &trace) catch 0) != 7 or trace != 31) return 108
|
||||||
|
|
||||||
|
# 9. Explicit errors run errdefer and expose the captured error.
|
||||||
|
trace = 0
|
||||||
|
if ((explicit_cleanup(true, &trace) catch 9) != 9 or trace != 321) return 109
|
||||||
|
|
||||||
|
# 10. Propagated errors run both errdefer and ordinary defer.
|
||||||
|
trace = 0
|
||||||
|
if ((try_cleanup(&trace) catch 9) != 9 or trace != 54) return 110
|
||||||
|
|
||||||
|
# 11. Captures observe the widened enclosing error type.
|
||||||
|
trace = 0
|
||||||
|
if ((widen_cleanup(&trace) catch 9) != 9 or trace != 6) return 111
|
||||||
|
|
||||||
|
# 12. An errdefer expires when its block exits normally.
|
||||||
|
trace = 0
|
||||||
|
if ((scoped_cleanup(&trace) catch 9) != 9 or trace != 7) return 112
|
||||||
|
|
||||||
|
# 13. One captured errdefer can be replayed at several distinct error exits.
|
||||||
|
trace = 0
|
||||||
|
_ = multi_exit_cleanup(true, &trace) catch 0
|
||||||
|
_ = multi_exit_cleanup(false, &trace) catch 0
|
||||||
|
if (trace != 16) return 113
|
||||||
|
|
||||||
return 42
|
return 42
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
id = "brolang"
|
id = "brolang"
|
||||||
name = "Brolang"
|
name = "Brolang"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
schema_version = 1
|
schema_version = 1
|
||||||
authors = ["Brolang contributors"]
|
authors = ["Brolang contributors"]
|
||||||
description = "Brolang language support"
|
description = "Brolang language support"
|
||||||
@@ -9,5 +9,5 @@ languages = ["languages/brolang"]
|
|||||||
|
|
||||||
[grammars.brolang]
|
[grammars.brolang]
|
||||||
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
|
||||||
rev = "zed-dev"
|
rev = "ba171a2d5e248fa24f5f4ee960e21056893f72e7"
|
||||||
path = "tree-sitter-brolang"
|
path = "tree-sitter-brolang"
|
||||||
|
|||||||
Binary file not shown.
@@ -23,6 +23,16 @@
|
|||||||
(builtin_type) @type.builtin
|
(builtin_type) @type.builtin
|
||||||
(named_type) @type
|
(named_type) @type
|
||||||
|
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
|
(struct_literal
|
||||||
|
type: (qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
(type_declaration name: (identifier) @type)
|
(type_declaration name: (identifier) @type)
|
||||||
(function_declaration name: (identifier) @function)
|
(function_declaration name: (identifier) @function)
|
||||||
(parameter name: (identifier) @variable.parameter)
|
(parameter name: (identifier) @variable.parameter)
|
||||||
@@ -62,6 +72,7 @@
|
|||||||
"break"
|
"break"
|
||||||
"continue"
|
"continue"
|
||||||
"defer"
|
"defer"
|
||||||
|
"errdefer"
|
||||||
"yield"
|
"yield"
|
||||||
"match"
|
"match"
|
||||||
"else"
|
"else"
|
||||||
@@ -86,6 +97,7 @@
|
|||||||
"/"
|
"/"
|
||||||
"!"
|
"!"
|
||||||
"&"
|
"&"
|
||||||
|
"@"
|
||||||
"?"
|
"?"
|
||||||
"^"
|
"^"
|
||||||
".."
|
".."
|
||||||
|
|||||||
@@ -278,7 +278,17 @@ module.exports = grammar({
|
|||||||
break_statement: $ => seq('break', optional(seq(':', field('label', $.identifier)))),
|
break_statement: $ => seq('break', optional(seq(':', field('label', $.identifier)))),
|
||||||
continue_statement: $ => seq('continue', optional(seq(':', field('label', $.identifier)))),
|
continue_statement: $ => seq('continue', optional(seq(':', field('label', $.identifier)))),
|
||||||
|
|
||||||
defer_statement: $ => seq('defer', repeat($._newline), field('body', $.statement)),
|
defer_statement: $ => choice(
|
||||||
|
seq('defer', repeat($._newline), field('body', $.statement)),
|
||||||
|
seq(
|
||||||
|
'errdefer',
|
||||||
|
repeat($._newline),
|
||||||
|
optional(seq(field('capture', $.error_capture), repeat($._newline))),
|
||||||
|
field('body', $.statement),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
error_capture: $ => seq('|', choice($.identifier, $.sink), '|'),
|
||||||
|
|
||||||
labeled_block: $ => seq(
|
labeled_block: $ => seq(
|
||||||
field('label', $.identifier),
|
field('label', $.identifier),
|
||||||
|
|||||||
@@ -23,6 +23,16 @@
|
|||||||
(builtin_type) @type.builtin
|
(builtin_type) @type.builtin
|
||||||
(named_type) @type
|
(named_type) @type
|
||||||
|
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
|
(struct_literal
|
||||||
|
type: (qualified_identifier
|
||||||
|
(identifier) @function .)
|
||||||
|
(argument_list))
|
||||||
|
|
||||||
(type_declaration name: (identifier) @type)
|
(type_declaration name: (identifier) @type)
|
||||||
(function_declaration name: (identifier) @function)
|
(function_declaration name: (identifier) @function)
|
||||||
(parameter name: (identifier) @variable.parameter)
|
(parameter name: (identifier) @variable.parameter)
|
||||||
@@ -62,6 +72,7 @@
|
|||||||
"break"
|
"break"
|
||||||
"continue"
|
"continue"
|
||||||
"defer"
|
"defer"
|
||||||
|
"errdefer"
|
||||||
"yield"
|
"yield"
|
||||||
"match"
|
"match"
|
||||||
"else"
|
"else"
|
||||||
@@ -86,6 +97,7 @@
|
|||||||
"/"
|
"/"
|
||||||
"!"
|
"!"
|
||||||
"&"
|
"&"
|
||||||
|
"@"
|
||||||
"?"
|
"?"
|
||||||
"^"
|
"^"
|
||||||
".."
|
".."
|
||||||
|
|||||||
@@ -1855,26 +1855,109 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"defer_statement": {
|
"defer_statement": {
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "defer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "body",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "statement"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "errdefer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CHOICE",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "SEQ",
|
||||||
|
"members": [
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "capture",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "error_capture"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "REPEAT",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "_newline"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "BLANK"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "FIELD",
|
||||||
|
"name": "body",
|
||||||
|
"content": {
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "statement"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"error_capture": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "defer"
|
"value": "|"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "REPEAT",
|
"type": "CHOICE",
|
||||||
"content": {
|
"members": [
|
||||||
"type": "SYMBOL",
|
{
|
||||||
"name": "_newline"
|
"type": "SYMBOL",
|
||||||
}
|
"name": "identifier"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "SYMBOL",
|
||||||
|
"name": "sink"
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "FIELD",
|
"type": "STRING",
|
||||||
"name": "body",
|
"value": "|"
|
||||||
"content": {
|
|
||||||
"type": "SYMBOL",
|
|
||||||
"name": "statement"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -517,6 +517,16 @@
|
|||||||
"named": true
|
"named": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"capture": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": false,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "error_capture",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -629,6 +639,25 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "error_capture",
|
||||||
|
"named": true,
|
||||||
|
"fields": {},
|
||||||
|
"children": {
|
||||||
|
"multiple": false,
|
||||||
|
"required": true,
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "identifier",
|
||||||
|
"named": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "sink",
|
||||||
|
"named": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "expression",
|
"type": "expression",
|
||||||
"named": true,
|
"named": true,
|
||||||
@@ -2471,6 +2500,10 @@
|
|||||||
"type": "enum",
|
"type": "enum",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "errdefer",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "escape_sequence",
|
"type": "escape_sequence",
|
||||||
"named": true
|
"named": true
|
||||||
|
|||||||
+112536
-105418
File diff suppressed because it is too large
Load Diff
@@ -95,3 +95,62 @@ sum func(a, b i32) i32 ! Status {
|
|||||||
(expression
|
(expression
|
||||||
(enum_literal
|
(enum_literal
|
||||||
(identifier))))))))))))
|
(identifier))))))))))))
|
||||||
|
|
||||||
|
==================
|
||||||
|
Errdefer
|
||||||
|
==================
|
||||||
|
|
||||||
|
Failure :: enum { bad }
|
||||||
|
|
||||||
|
work func() i32 ! Failure {
|
||||||
|
errdefer cleanup()
|
||||||
|
errdefer |err| {
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
(source_file
|
||||||
|
(type_declaration
|
||||||
|
(identifier)
|
||||||
|
(enum_type
|
||||||
|
(enum_body
|
||||||
|
(enum_member
|
||||||
|
(identifier)))))
|
||||||
|
(function_declaration
|
||||||
|
(identifier)
|
||||||
|
(parameter_list)
|
||||||
|
(type
|
||||||
|
(builtin_type))
|
||||||
|
(type
|
||||||
|
(named_type
|
||||||
|
(qualified_identifier
|
||||||
|
(identifier))))
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(defer_statement
|
||||||
|
(statement
|
||||||
|
(expression_statement
|
||||||
|
(expression
|
||||||
|
(call_expression
|
||||||
|
(expression
|
||||||
|
(identifier))
|
||||||
|
(argument_list)))))))
|
||||||
|
(statement
|
||||||
|
(defer_statement
|
||||||
|
(error_capture
|
||||||
|
(identifier))
|
||||||
|
(statement
|
||||||
|
(block
|
||||||
|
(statement
|
||||||
|
(assignment_statement
|
||||||
|
(expression
|
||||||
|
(sink))
|
||||||
|
(expression
|
||||||
|
(identifier))))))))
|
||||||
|
(statement
|
||||||
|
(return_statement
|
||||||
|
(expression
|
||||||
|
(integer)))))))
|
||||||
|
|||||||
Reference in New Issue
Block a user