1
//! UNWIND-PROTECT — try_table-based guaranteed cleanup. Compile + validate
2
//! surface; runtime evaluation (cleanup firing on both paths, re-raise) is
3
//! covered in `nms/tests/unwind_protect_runtime.rs`.
4

            
5
use super::common::{compile_and_validate, compile_expect_error};
6

            
7
#[test]
8
1
fn unwind_protect_constant_body_validates() {
9
1
    compile_and_validate(r#"(unwind-protect 42 (debug "cleanup"))"#);
10
1
}
11

            
12
#[test]
13
1
fn unwind_protect_raising_body_validates() {
14
    // Body raises; the form catches, runs cleanup, re-raises. Valid module.
15
1
    compile_and_validate(r#"(unwind-protect (error 'boom "x") (debug "cleanup"))"#);
16
1
}
17

            
18
#[test]
19
1
fn unwind_protect_string_body_validates() {
20
1
    compile_and_validate(r#"(unwind-protect "ok" (debug "c"))"#);
21
1
}
22

            
23
#[test]
24
1
fn unwind_protect_multiple_cleanup_forms_validate() {
25
1
    compile_and_validate(r#"(unwind-protect 1 (debug "a") (debug "b"))"#);
26
1
}
27

            
28
#[test]
29
1
fn unwind_protect_no_cleanup_validates() {
30
    // Zero cleanup forms degenerates to "evaluate body; re-raise on throw".
31
1
    compile_and_validate("(unwind-protect 1)");
32
1
}
33

            
34
#[test]
35
1
fn unwind_protect_effect_position_validates() {
36
1
    compile_and_validate(r#"(begin (unwind-protect 1 (debug "c")) 5)"#);
37
1
}
38

            
39
#[test]
40
1
fn unwind_protect_setf_cleanup_validates() {
41
1
    compile_and_validate("(let* ((n 0)) (unwind-protect 1 (setf n 9)) n)");
42
1
}
43

            
44
#[test]
45
1
fn nested_unwind_protect_validates() {
46
1
    compile_and_validate(
47
1
        r#"(unwind-protect (unwind-protect (error 'x "m") (debug "inner")) (debug "outer"))"#,
48
    );
49
1
}
50

            
51
#[test]
52
1
fn unwind_protect_inside_handler_case_validates() {
53
1
    compile_and_validate(r#"(handler-case (unwind-protect (error 'b "m") (debug "c")) (b (e) 7))"#);
54
1
}
55

            
56
#[test]
57
1
fn unwind_protect_requires_a_body() {
58
1
    let err = compile_expect_error("(unwind-protect)");
59
1
    assert!(
60
1
        err.contains("unwind-protect") && err.contains("body"),
61
        "expected a missing-body error, got: {err}"
62
    );
63
1
}
64

            
65
#[test]
66
1
fn return_from_inside_unwind_protect_body_resolves() {
67
    // A `(return-from)` inside the protected body resolves its relative `br`
68
    // through the +3 wrapper frames unwind-protect opens.
69
1
    compile_and_validate(r#"(block done (unwind-protect (return-from done 1) (debug "c")))"#);
70
1
}
71

            
72
#[test]
73
1
fn nonlocal_return_from_emits_crossing_cleanup_and_validates() {
74
    // A `(return-from out)` crossing the unwind-protect must emit the cleanup
75
    // inline before its `br` (CL unwind semantics) and still produce a valid
76
    // module. The trailing 99 inside the block is dead after the exit.
77
1
    compile_and_validate(r#"(block out (unwind-protect (return-from out 7) (debug "c")) 99)"#);
78
1
}
79

            
80
#[test]
81
1
fn nonlocal_go_crossing_unwind_protect_validates() {
82
    // A `(go)` to an outer-tagbody tag crosses the unwind-protect; the crossed
83
    // cleanup is emitted before the `br` to the dispatcher loop.
84
1
    compile_and_validate(
85
1
        r#"(tagbody (unwind-protect (go done) (debug "c")) (debug "skipped") done)"#,
86
    );
87
1
}
88

            
89
#[test]
90
1
fn unwind_protect_go_body_validates() {
91
    // A bare `(go)` as the protected body — diverges with no stack value;
92
    // GO's stack variant + the trailing unreachable keep the module valid.
93
1
    compile_and_validate(r#"(tagbody (unwind-protect (go done) (debug "c")) done)"#);
94
1
}