1
//! `(error 'code "message")` special form. Validates the compile-side
2
//! surface: arity rejection, type rejection (non-symbol code arg), and
3
//! that the eval-mode module still declares the `__nomi_raise` import.
4
//!
5
//! Since Tier 3 (ADR-0026) `(error)` lowers to a native `throw
6
//! $nomi_error`, not a direct `call __nomi_raise`. The import is still
7
//! present because the compiler-emitted boundary `try_table` around the
8
//! body bridges an uncaught throw to `__nomi_raise` — so the
9
//! import-presence assertion still holds, now via the boundary handler.
10
//! Runtime classification is covered by the rpc-side raise unit tests +
11
//! integration tests.
12

            
13
use nomiscript::{Compiler, Program, Reader, SymbolTable};
14
use wasmparser::{Validator, WasmFeatures};
15

            
16
2
fn validate(wasm: &[u8]) {
17
2
    let f = WasmFeatures::default()
18
2
        | WasmFeatures::GC
19
2
        | WasmFeatures::REFERENCE_TYPES
20
2
        | WasmFeatures::FUNCTION_REFERENCES
21
2
        | WasmFeatures::EXCEPTIONS;
22
2
    Validator::new_with_features(f)
23
2
        .validate_all(wasm)
24
2
        .expect("wasm validation failed");
25
2
}
26

            
27
2
fn compile_eval(src: &str) -> Vec<u8> {
28
2
    let program: Program = Reader::parse(src).expect("parse");
29
2
    let mut compiler = Compiler::with_host_fns(Vec::new());
30
2
    let mut symbols = SymbolTable::with_builtins();
31
2
    let (bytes, _ty) = compiler
32
2
        .compile_eval_with_type(&program, &mut symbols)
33
2
        .unwrap_or_else(|e| panic!("compile {src:?}: {e}"));
34
2
    validate(&bytes);
35
2
    bytes
36
2
}
37

            
38
4
fn compile_expect_error(src: &str) -> String {
39
4
    let program: Program = Reader::parse(src).expect("parse");
40
4
    let mut compiler = Compiler::with_host_fns(Vec::new());
41
4
    let mut symbols = SymbolTable::with_builtins();
42
4
    match compiler.compile_eval_with_type(&program, &mut symbols) {
43
        Ok(_) => panic!("expected compile error for {src:?}"),
44
4
        Err(e) => e.to_string(),
45
    }
46
4
}
47

            
48
2
fn module_imports_nomi_raise(wasm: &[u8]) -> bool {
49
    // Wasm import-section names are stored as raw byte strings — both
50
    // module ("nomi") and field ("__nomi_raise") appear verbatim in
51
    // the binary, so a substring scan is enough to confirm the import
52
    // was emitted without coupling to wasmparser's import-iter shape.
53
2
    wasm.windows(b"__nomi_raise".len())
54
874
        .any(|w| w == b"__nomi_raise")
55
2
}
56

            
57
#[test]
58
1
fn error_form_module_declares_nomi_raise_boundary_import() {
59
1
    let wasm = compile_eval(r#"(error 'no-such-account "id=42")"#);
60
1
    assert!(
61
1
        module_imports_nomi_raise(&wasm),
62
        "module must declare a `nomi.__nomi_raise` import (the boundary bridge)"
63
    );
64
1
}
65

            
66
#[test]
67
1
fn error_form_zero_args_is_arity_error() {
68
1
    let err = compile_expect_error("(error)");
69
1
    assert!(err.contains("Arity") || err.contains("error"), "got: {err}");
70
1
}
71

            
72
#[test]
73
1
fn error_form_one_arg_is_arity_error() {
74
1
    let err = compile_expect_error("(error 'just-code)");
75
1
    assert!(err.contains("Arity") || err.contains("error"), "got: {err}");
76
1
}
77

            
78
#[test]
79
1
fn error_form_three_args_is_arity_error() {
80
1
    let err = compile_expect_error(r#"(error 'a "b" "c")"#);
81
1
    assert!(err.contains("Arity") || err.contains("error"), "got: {err}");
82
1
}
83

            
84
#[test]
85
1
fn error_form_unquoted_first_arg_is_type_error() {
86
1
    let err = compile_expect_error(r#"(error "string-not-symbol" "msg")"#);
87
1
    assert!(
88
1
        err.contains("Type") || err.contains("quoted symbol"),
89
        "got: {err}"
90
    );
91
1
}
92

            
93
#[test]
94
1
fn error_form_runtime_message_arg_compiles() {
95
    // Message can be any StringRef-typed expression — most often a
96
    // literal, but a let-bound runtime string must work too.
97
1
    let wasm = compile_eval(r#"(let* ((m "boom")) (error 'oops m))"#);
98
1
    assert!(module_imports_nomi_raise(&wasm));
99
1
}