1
//! Regression tests for compiler/codegen drift bugs. Each test
2
//! reproduces a class of bug where eval-time type inference disagreed
3
//! with what codegen actually emits. These run inside the same
4
//! integration-test binary as the categorized `codegen::*` suite to
5
//! share infrastructure.
6
//!
7
//! Historical bug list (all fixed):
8
//!
9
//! 1. `compile_for_stack(Expr::WasmRuntime(_))` returned the type
10
//!    without emitting wasm — used to fire via lambda-param binding
11
//!    and via defvar/defparameter init paths. Both are gone:
12
//!    `compile_for_stack` refuses the placeholder, and every binder
13
//!    promotes a runtime-typed init into a wasm local.
14
//! 2. `do_form` / `do_star_form` returned hardcoded `PairRef(I32)`
15
//!    for the runtime branch — codegen actually allocates the pair
16
//!    element from the body's setf-cons car.
17
//! 3. `eval_body` runtime-promotion fallback hardcoded `PairRef(I32)`
18
//!    — codegen now infers String/Bytes/WasmLocal types correctly.
19
//! 4. `compile_for_effect` didn't dispatch DEFVAR/DEFPARAMETER to
20
//!    their compile-side wrappers — runtime-init promotion silently
21
//!    skipped.
22

            
23
use nomiscript::{Compiler, Reader, SymbolTable};
24
use wasmparser::{Validator, WasmFeatures};
25

            
26
5
fn validate(wasm: &[u8]) {
27
5
    let features = WasmFeatures::default()
28
5
        | WasmFeatures::GC
29
5
        | WasmFeatures::REFERENCE_TYPES
30
5
        | WasmFeatures::FUNCTION_REFERENCES
31
5
        | WasmFeatures::EXCEPTIONS;
32
5
    Validator::new_with_features(features)
33
5
        .validate_all(wasm)
34
5
        .expect("wasm validation failed");
35
5
}
36

            
37
5
fn compile(src: &str) -> Vec<u8> {
38
5
    let program = Reader::parse(src).unwrap_or_else(|e| panic!("parse {src:?}: {e}"));
39
5
    let mut symbols = SymbolTable::with_builtins_for_wasm();
40
5
    let mut compiler = Compiler::new();
41
5
    compiler
42
5
        .compile(&program, &mut symbols)
43
5
        .unwrap_or_else(|e| panic!("compile {src:?}: {e}"))
44
5
}
45

            
46
/// `do_star_form` (and `do_form`) must derive the static
47
/// `PairRef(elem)` shape from the body-setf's cons car. A
48
/// hardcoded `PairElement::I32` disagreed with the Ratio cars the
49
/// codegen actually emits, surfacing as a `ref.cast i31` trap or a
50
/// downstream type-mismatch refusal.
51
#[test]
52
1
fn do_star_with_ratio_accumulator_composes_with_car_plus_arith() {
53
1
    let wasm = compile("(+ (car (do* ((i 0 (+ i 1)) (acc nil (cons i acc))) ((= i 3) acc))) 1)");
54
1
    validate(&wasm);
55
1
}
56

            
57
#[test]
58
1
fn do_with_ratio_accumulator_composes_with_car_plus_arith() {
59
1
    let wasm = compile("(+ (car (do ((i 0 (+ i 1)) (acc nil (cons i acc))) ((= i 3) acc))) 1)");
60
1
    validate(&wasm);
61
1
}
62

            
63
/// A `let` body whose tail is a String literal must report
64
/// `StringRef` as its runtime-promoted type. The legacy fallback
65
/// hardcoded `PairRef(I32)`, which made `(cons (let ...) nil)` see a
66
/// pair-of-pair and refuse at compile time. The runtime side-effect
67
/// trigger uses a real host fn (`(entity-count)`) so the promotion
68
/// path matches the one production scripts hit.
69
#[test]
70
1
fn let_with_runtime_prefix_and_string_tail_infers_stringref() {
71
    // `(entity-count)` makes the body has-runtime; the tail is a
72
    // String literal so the promoted type must be StringRef and the
73
    // outer cons accepts it.
74
1
    let wasm = compile("(cons (let ((y 1)) (entity-count) \"hi\") nil)");
75
1
    validate(&wasm);
76
1
}
77

            
78
/// `(defvar n (entity-count))` binds `n` to a runtime i32. The
79
/// compile-side wrapper must allocate a wasm local and emit the
80
/// init's wasm into it; otherwise later uses resolve to a
81
/// `WasmRuntime` placeholder and `compile_for_stack` (after the
82
/// hardening) refuses them.
83
#[test]
84
1
fn defvar_with_runtime_init_used_later_validates() {
85
1
    let wasm = compile("(defvar n (entity-count)) (= n 0)");
86
1
    validate(&wasm);
87
1
}
88

            
89
#[test]
90
1
fn defparameter_with_runtime_init_used_later_validates() {
91
1
    let wasm = compile("(defparameter n (entity-count)) (= n 0)");
92
1
    validate(&wasm);
93
1
}
94

            
95
/// Sanity check that `wasmparser` rejects malformed wasm. If this
96
/// ever passes (validator accepts garbage), every other test in this
97
/// file is a no-op.
98
#[test]
99
1
fn validator_rejects_truncated_wasm() {
100
1
    let features = WasmFeatures::default()
101
1
        | WasmFeatures::GC
102
1
        | WasmFeatures::REFERENCE_TYPES
103
1
        | WasmFeatures::FUNCTION_REFERENCES
104
1
        | WasmFeatures::EXCEPTIONS;
105
1
    let broken: &[u8] = b"\0asm\x01\x00\x00\x00\xff\xff";
106
1
    let r = Validator::new_with_features(features).validate_all(broken);
107
1
    assert!(r.is_err(), "broken trailing bytes must fail validation");
108
1
}