errdefer and try/defer fix

This commit is contained in:
2026-07-13 19:20:22 +02:00
parent 9e75549d02
commit 6de4d9f9f3
23 changed files with 113190 additions and 105482 deletions
+34
View File
@@ -91,6 +91,36 @@ use_try func() i32 ! Error {
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 {
return may_fail(true) catch |e| {
match e {
@@ -135,6 +165,7 @@ storage_mutation func() i32 {
}
GLOBAL :: $sum_loop(4)
ERRDEFER :: $ct_errdefer_check()
main func() i32 {
point Point :: $make_point()
@@ -205,5 +236,8 @@ main func() i32 {
if fallible_err != 13 {
return 16
}
if ERRDEFER != 42 {
return 17
}
return 0
}
+87 -1
View File
@@ -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
# (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
}
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 {
# 1. return value captured before defers run.
if (spill_check() != 5) return 101
@@ -78,5 +138,31 @@ main func() i32 {
# 7. break does not run an enclosing function-scope defer.
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
}