Lines
100 %
Functions
80 %
Branches
//! Regression tests for compiler/codegen drift bugs. Each test
//! reproduces a class of bug where eval-time type inference disagreed
//! with what codegen actually emits. These run inside the same
//! integration-test binary as the categorized `codegen::*` suite to
//! share infrastructure.
//!
//! Historical bug list (all fixed):
//! 1. `compile_for_stack(Expr::WasmRuntime(_))` returned the type
//! without emitting wasm — used to fire via lambda-param binding
//! and via defvar/defparameter init paths. Both are gone:
//! `compile_for_stack` refuses the placeholder, and every binder
//! promotes a runtime-typed init into a wasm local.
//! 2. `do_form` / `do_star_form` returned hardcoded `PairRef(I32)`
//! for the runtime branch — codegen actually allocates the pair
//! element from the body's setf-cons car.
//! 3. `eval_body` runtime-promotion fallback hardcoded `PairRef(I32)`
//! — codegen now infers String/Bytes/WasmLocal types correctly.
//! 4. `compile_for_effect` didn't dispatch DEFVAR/DEFPARAMETER to
//! their compile-side wrappers — runtime-init promotion silently
//! skipped.
use nomiscript::{Compiler, Reader, SymbolTable};
use wasmparser::{Validator, WasmFeatures};
fn validate(wasm: &[u8]) {
let features = WasmFeatures::default()
| WasmFeatures::GC
| WasmFeatures::REFERENCE_TYPES
| WasmFeatures::FUNCTION_REFERENCES
| WasmFeatures::EXCEPTIONS;
Validator::new_with_features(features)
.validate_all(wasm)
.expect("wasm validation failed");
}
fn compile(src: &str) -> Vec<u8> {
let program = Reader::parse(src).unwrap_or_else(|e| panic!("parse {src:?}: {e}"));
let mut symbols = SymbolTable::with_builtins_for_wasm();
let mut compiler = Compiler::new();
compiler
.compile(&program, &mut symbols)
.unwrap_or_else(|e| panic!("compile {src:?}: {e}"))
/// `do_star_form` (and `do_form`) must derive the static
/// `PairRef(elem)` shape from the body-setf's cons car. A
/// hardcoded `PairElement::I32` disagreed with the Ratio cars the
/// codegen actually emits, surfacing as a `ref.cast i31` trap or a
/// downstream type-mismatch refusal.
#[test]
fn do_star_with_ratio_accumulator_composes_with_car_plus_arith() {
let wasm = compile("(+ (car (do* ((i 0 (+ i 1)) (acc nil (cons i acc))) ((= i 3) acc))) 1)");
validate(&wasm);
fn do_with_ratio_accumulator_composes_with_car_plus_arith() {
let wasm = compile("(+ (car (do ((i 0 (+ i 1)) (acc nil (cons i acc))) ((= i 3) acc))) 1)");
/// A `let` body whose tail is a String literal must report
/// `StringRef` as its runtime-promoted type. The legacy fallback
/// hardcoded `PairRef(I32)`, which made `(cons (let ...) nil)` see a
/// pair-of-pair and refuse at compile time. The runtime side-effect
/// trigger uses a real host fn (`(entity-count)`) so the promotion
/// path matches the one production scripts hit.
fn let_with_runtime_prefix_and_string_tail_infers_stringref() {
// `(entity-count)` makes the body has-runtime; the tail is a
// String literal so the promoted type must be StringRef and the
// outer cons accepts it.
let wasm = compile("(cons (let ((y 1)) (entity-count) \"hi\") nil)");
/// `(defvar n (entity-count))` binds `n` to a runtime i32. The
/// compile-side wrapper must allocate a wasm local and emit the
/// init's wasm into it; otherwise later uses resolve to a
/// `WasmRuntime` placeholder and `compile_for_stack` (after the
/// hardening) refuses them.
fn defvar_with_runtime_init_used_later_validates() {
let wasm = compile("(defvar n (entity-count)) (= n 0)");
fn defparameter_with_runtime_init_used_later_validates() {
let wasm = compile("(defparameter n (entity-count)) (= n 0)");
/// Sanity check that `wasmparser` rejects malformed wasm. If this
/// ever passes (validator accepts garbage), every other test in this
/// file is a no-op.
fn validator_rejects_truncated_wasm() {
let broken: &[u8] = b"\0asm\x01\x00\x00\x00\xff\xff";
let r = Validator::new_with_features(features).validate_all(broken);
assert!(r.is_err(), "broken trailing bytes must fail validation");