Lines
100 %
Functions
Branches
//! Constant-folding regressions: fully-static expressions must
//! evaluate at compile time to the same `Value` the runtime would
//! produce, and the wasm emitted for them must validate.
use nomiscript::{Fraction, Reader, SymbolTable, Value, eval_program};
use super::common::{compile_and_validate, compile_expect_error};
fn eval_to_value(symbols: &mut SymbolTable, code: &str) -> Value {
let program = Reader::parse(code).unwrap();
eval_program(symbols, &program).unwrap()
}
#[test]
fn add_constant_fold() {
let mut symbols = SymbolTable::with_builtins();
assert_eq!(
eval_to_value(&mut symbols, "(+ 1 2 3)"),
Value::Number(Fraction::from_integer(6)),
);
fn sub_constant_fold() {
eval_to_value(&mut symbols, "(- 10 3)"),
Value::Number(Fraction::from_integer(7)),
fn mul_constant_fold() {
eval_to_value(&mut symbols, "(* 3 4)"),
Value::Number(Fraction::from_integer(12)),
fn integer_div_constant_fold_truncates() {
// ADR-0028: integer literals are Index, so `(/ 10 3)` is integer division
// (truncating toward zero, matching `i32.div_s`) — not the old rational 10/3.
eval_to_value(&mut symbols, "(/ 10 3)"),
Value::Number(Fraction::from_integer(3)),
fn ratio_arithmetic_constant_fold() {
// Rational arithmetic uses fractional literals (`1/3`, the Scalar idiom);
// `(/ 1 3)` would now be integer division (Index → 0).
eval_to_value(&mut symbols, "(+ 1/3 1/4)"),
Value::Number(Fraction::new(7, 12)),
fn comparison_constant_fold() {
assert_eq!(eval_to_value(&mut symbols, "(= 1 1)"), Value::Bool(true));
assert_eq!(eval_to_value(&mut symbols, "(< 1 2)"), Value::Bool(true));
assert_eq!(eval_to_value(&mut symbols, "(> 3 2)"), Value::Bool(true));
fn compile_constant_arithmetic() {
compile_and_validate("(+ (/ 1 3) (/ 1 4))");
fn compile_constant_comparison() {
compile_and_validate("(= 1 2)");
fn compile_nested_arithmetic() {
compile_and_validate("(* (+ 1 2) (- 5 3))");
/// Regression (AFL): const-folding arithmetic over `Ratio<i64>` used the bare
/// `+`/`-`/`*`/`/` operators, which panic (debug / overflow-checks) or wrap
/// (release) when a cross-multiply exceeds i64. An all-literal overflowing
/// expression must now surface a structured compile error, never a SIGABRT.
fn constant_arithmetic_overflow_is_a_compile_error_not_a_panic() {
for src in [
"(* 9999999999 9999999999)",
"(+ 1/9999999999 1/9999999998)",
"(- 1/9999999999 1/9999999998)",
"(/ 1/9999999999 9999999999)",
"(mod 1/9999999999 7777777777)",
] {
let err = compile_expect_error(src);
assert!(
err.contains("overflow"),
"{src} should report an overflow compile error, got: {err}"
/// Regression (adversarial review): integer `MOD` const-folding used the raw
/// Rust `%`, which panics on `i64::MIN % -1`. wasm `i32.rem_s` defines that case
/// as 0 (it does NOT trap), so the fold must yield 0 to stay in lockstep with
/// codegen — never panic. `i64::MIN` is reached by folding `(- 0 i64::MAX 1)`
/// since the bare `MIN` literal is out of the reader's range.
fn integer_mod_min_by_neg_one_folds_to_zero_not_panic() {
eval_to_value(&mut symbols, "(mod (- 0 9223372036854775807 1) -1)"),
Value::Number(Fraction::from_integer(0)),
// And the same form compiles to validating wasm (no panic on the fold path).
compile_and_validate("(mod (- 0 9223372036854775807 1) -1)");