Lines
100 %
Functions
Branches
//! UNWIND-PROTECT — try_table-based guaranteed cleanup. Compile + validate
//! surface; runtime evaluation (cleanup firing on both paths, re-raise) is
//! covered in `nms/tests/unwind_protect_runtime.rs`.
use super::common::{compile_and_validate, compile_expect_error};
#[test]
fn unwind_protect_constant_body_validates() {
compile_and_validate(r#"(unwind-protect 42 (debug "cleanup"))"#);
}
fn unwind_protect_raising_body_validates() {
// Body raises; the form catches, runs cleanup, re-raises. Valid module.
compile_and_validate(r#"(unwind-protect (error 'boom "x") (debug "cleanup"))"#);
fn unwind_protect_string_body_validates() {
compile_and_validate(r#"(unwind-protect "ok" (debug "c"))"#);
fn unwind_protect_multiple_cleanup_forms_validate() {
compile_and_validate(r#"(unwind-protect 1 (debug "a") (debug "b"))"#);
fn unwind_protect_no_cleanup_validates() {
// Zero cleanup forms degenerates to "evaluate body; re-raise on throw".
compile_and_validate("(unwind-protect 1)");
fn unwind_protect_effect_position_validates() {
compile_and_validate(r#"(begin (unwind-protect 1 (debug "c")) 5)"#);
fn unwind_protect_setf_cleanup_validates() {
compile_and_validate("(let* ((n 0)) (unwind-protect 1 (setf n 9)) n)");
fn nested_unwind_protect_validates() {
compile_and_validate(
r#"(unwind-protect (unwind-protect (error 'x "m") (debug "inner")) (debug "outer"))"#,
);
fn unwind_protect_inside_handler_case_validates() {
compile_and_validate(r#"(handler-case (unwind-protect (error 'b "m") (debug "c")) (b (e) 7))"#);
fn unwind_protect_requires_a_body() {
let err = compile_expect_error("(unwind-protect)");
assert!(
err.contains("unwind-protect") && err.contains("body"),
"expected a missing-body error, got: {err}"
fn return_from_inside_unwind_protect_body_resolves() {
// A `(return-from)` inside the protected body resolves its relative `br`
// through the +3 wrapper frames unwind-protect opens.
compile_and_validate(r#"(block done (unwind-protect (return-from done 1) (debug "c")))"#);
fn nonlocal_return_from_emits_crossing_cleanup_and_validates() {
// A `(return-from out)` crossing the unwind-protect must emit the cleanup
// inline before its `br` (CL unwind semantics) and still produce a valid
// module. The trailing 99 inside the block is dead after the exit.
compile_and_validate(r#"(block out (unwind-protect (return-from out 7) (debug "c")) 99)"#);
fn nonlocal_go_crossing_unwind_protect_validates() {
// A `(go)` to an outer-tagbody tag crosses the unwind-protect; the crossed
// cleanup is emitted before the `br` to the dispatcher loop.
r#"(tagbody (unwind-protect (go done) (debug "c")) (debug "skipped") done)"#,
fn unwind_protect_go_body_validates() {
// A bare `(go)` as the protected body — diverges with no stack value;
// GO's stack variant + the trailing unreachable keep the module valid.
compile_and_validate(r#"(tagbody (unwind-protect (go done) (debug "c")) done)"#);