Files
RustAst/src/integration_test.rs
T
Michael Schimmel 2e8d5284c2 Feat: Enable nested destructuring optimization
This commit introduces optimizations for nested destructuring, allowing
tuples and records to be flattened and matched directly against function
arguments. This significantly improves performance by enabling more
constant folding and reducing intermediate allocations.

The changes include:
- Modifying the `Binder` to correctly count nested parameters.
- Enhancing `flatten_tuple` in the `Optimizer` to handle records and NOP
  nodes.
- Updating `map_params_to_args` to recursively destructure nested
  compound arguments.
- Adding integration tests to verify the correctness of tuple-to-tuple
  and record-to-tuple destructuring optimizations.
2026-02-22 16:34:40 +01:00

299 lines
10 KiB
Rust

#[cfg(test)]
mod tests {
use crate::ast::environment::Environment;
use crate::ast::nodes::UntypedKind;
use crate::ast::parser::Parser;
use crate::ast::types::Value;
#[test]
fn test_parse_integer_constant() {
let source = "123";
let mut parser = Parser::new(source).expect("Failed to create parser");
let ast = parser.parse_expression().expect("Failed to parse");
if let UntypedKind::Constant(Value::Int(val)) = ast.kind {
assert_eq!(val, 123);
} else {
panic!("Expected Integer constant, got {:?}", ast.kind);
}
}
#[test]
fn test_parse_negative_integer() {
let source = "-42";
let mut parser = Parser::new(source).expect("Failed to create parser");
let ast = parser.parse_expression().expect("Failed to parse");
if let UntypedKind::Constant(Value::Int(val)) = ast.kind {
assert_eq!(val, -42);
} else {
panic!("Expected Integer constant, got {:?}", ast.kind);
}
}
#[test]
fn test_parse_float_constant() {
let source = "123.45";
let mut parser = Parser::new(source).expect("Failed to create parser");
let ast = parser.parse_expression().expect("Failed to parse");
if let UntypedKind::Constant(Value::Float(val)) = ast.kind {
assert_eq!(val, 123.45);
} else {
panic!("Expected Float constant, got {:?}", ast.kind);
}
}
#[test]
fn test_parse_negative_float() {
let source = "-10.5";
let mut parser = Parser::new(source).expect("Failed to create parser");
let ast = parser.parse_expression().expect("Failed to parse");
if let UntypedKind::Constant(Value::Float(val)) = ast.kind {
assert_eq!(val, -10.5);
} else {
panic!("Expected Float constant, got {:?}", ast.kind);
}
}
#[test]
fn test_closure_modification_from_source() {
let source = r#"
(do
(def x 10)
(def f (fn [] (assign x 20)))
(f)
x
)
"#;
let env = Environment::new();
let compiled = env.compile(source).expect("Failed to compile");
let linked = env.link(compiled);
let func = env.instantiate(linked);
let result: Result<Value, String> = Ok((func.func)(vec![]));
match result {
Ok(Value::Int(20)) => (),
Ok(val) => panic!("Expected Int(20), got {:?}", val),
Err(e) => panic!("VM Error: {}", e),
}
}
#[test]
fn test_examples() {
for opt in [false, true] {
let results = crate::utils::tester::run_functional_tests_with_optimization(opt);
for res in results {
assert!(
res.success,
"Example {} failed at opt {}: {}",
res.name, opt, res.message
);
}
}
}
#[test]
fn test_debug_mode_logging() {
let mut env = Environment::new();
env.optimization = false;
let source = "(+ 10 20)";
let result = env.run_debug(source).expect("Failed to run debug");
let (val, logs) = result;
// 1. Check value
match val {
Ok(Value::Int(30)) => (),
_ => panic!("Expected Int(30), got {:?}", val),
}
// 2. Check logs (should have entries for + and constants)
assert!(!logs.is_empty(), "Logs should not be empty");
// Look for typical trace patterns
let has_call = logs.iter().any(|l| l.contains("CALL"));
let has_const = logs.iter().any(|l| l.contains("CONST(10)"));
let has_result = logs.iter().any(|l| l.contains("} -> 30"));
assert!(has_call, "Logs should contain CALL");
assert!(has_const, "Logs should contain CONST(10)");
assert!(has_result, "Logs should contain result 30");
}
#[test]
fn test_rtl_operators() {
let env = Environment::new();
// --- Arithmetic ---
assert_eq!(format!("{}", env.run_script("(+ 10 20)").unwrap()), "30");
assert_eq!(format!("{}", env.run_script("(- 20 10)").unwrap()), "10");
assert_eq!(format!("{}", env.run_script("(* 10 20)").unwrap()), "200");
assert_eq!(format!("{}", env.run_script("(/ 20 10)").unwrap()), "2");
assert_eq!(format!("{}", env.run_script("(// 20 3)").unwrap()), "6"); // 20 // 3 = 6
assert_eq!(format!("{}", env.run_script("(% 20 3)").unwrap()), "2"); // 20 % 3 = 2
// --- Logic / Bitwise ---
assert_eq!(
format!("{}", env.run_script("(and true false)").unwrap()),
"false"
);
assert_eq!(
format!("{}", env.run_script("(or true false)").unwrap()),
"true"
);
assert_eq!(
format!("{}", env.run_script("(xor true false)").unwrap()),
"true"
);
assert_eq!(
format!("{}", env.run_script("(not true)").unwrap()),
"false"
);
assert_eq!(format!("{}", env.run_script("(<< 1 2)").unwrap()), "4"); // 1 << 2 = 4
assert_eq!(format!("{}", env.run_script("(>> 4 1)").unwrap()), "2"); // 4 >> 1 = 2
assert_eq!(format!("{}", env.run_script("(and 3 1)").unwrap()), "1"); // 3 & 1 = 1
// --- Comparison ---
assert_eq!(format!("{}", env.run_script("(= 10 10)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(= 10 20)").unwrap()), "false");
assert_eq!(format!("{}", env.run_script("(<> 10 20)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(< 10 20)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(> 10 20)").unwrap()), "false");
assert_eq!(format!("{}", env.run_script("(<= 10 10)").unwrap()), "true");
assert_eq!(format!("{}", env.run_script("(>= 10 10)").unwrap()), "true");
// --- NaN ---
assert_eq!(format!("{}", env.run_script("NaN").unwrap()), "NaN");
}
#[test]
fn test_random_isolation_between_environments() {
let env1 = Environment::new();
let env2 = Environment::new();
// 1. Seed env1
env1.run_script("(seed! 123)").unwrap();
let val1_a = env1.run_script("(random)").unwrap();
// 2. env2 should have its own default seed state
let val2_a = env2.run_script("(random)").unwrap();
// They are highly unlikely to be equal by default,
// and seeding env1 MUST not have seeded env2.
assert_ne!(
val1_a, val2_a,
"Environments must have isolated PRNG states"
);
// 3. Seed env2 differently
env2.run_script("(seed! 123)").unwrap();
let val2_b = env2.run_script("(random)").unwrap();
// After same seeding, they should match (isolated but identical seed)
assert_eq!(
val1_a, val2_b,
"Different environments with the same seed must produce the same sequence"
);
}
#[test]
fn test_random_seeding_determinism() {
let env = Environment::new();
// 1. First run with seed 42
env.run_script("(seed! 42)").unwrap();
let val1 = env.run_script("(random)").unwrap();
// 2. Second run with same seed 42
env.run_script("(seed! 42)").unwrap();
let val2 = env.run_script("(random)").unwrap();
assert_eq!(
val1, val2,
"Random results must be identical for the same seed"
);
// 3. Third run with different seed
env.run_script("(seed! 123)").unwrap();
let val3 = env.run_script("(random)").unwrap();
assert_ne!(val1, val3, "Random results must differ for different seeds");
}
#[test]
fn test_now_function_not_folded() {
let env = Environment::new();
let source = "(now)";
// 1. Check result type and value plausibility
let result = env.run_script(source).expect("Failed to run script");
if let Value::DateTime(ts) = result {
let current = chrono::Utc::now().timestamp_millis();
assert!(ts > 0);
assert!(ts <= current);
} else {
panic!("Expected DateTime, got {:?}", result);
}
// 2. Verify it's NOT constant folded in the AST dump
let dump = env.dump_ast(source).expect("Failed to dump AST");
assert!(
dump.contains("Call"),
"now() should remain a Call, not a Constant. Dump: \n{}",
dump
);
assert!(
!dump.contains("Constant: #"),
"now() should NOT be folded into a specific timestamp constant. Dump: \n{}",
dump
);
}
#[test]
fn test_date_parsing() {
let env = Environment::new();
let res = env.run_script("(date \"2023-01-01\")").unwrap();
if let Value::DateTime(_) = res {
// OK
} else {
panic!("Expected DateTime, got {:?}", res);
}
}
#[test]
fn test_dynamic_call_destructuring_underflow() {
let env = Environment::new();
let source = "(do
(def call-dynamic (fn [f data] (f data)))
(def data [10 [20 30]])
(def x (fn [[a [b c]]] (+ a (+ b c))))
(call-dynamic x data))";
let result = env.run_script(source);
if let Err(e) = &result {
panic!("Failed: {}", e);
}
assert_eq!(format!("{}", result.unwrap()), "60");
}
#[test]
fn test_nested_destructuring_optimization() {
let env = Environment::new();
// 1. Tuple-to-Tuple
let source_tuple = "((fn [[x y]] (+ x y)) [10 20])";
assert_eq!(format!("{}", env.run_script(source_tuple).unwrap()), "30");
let dump_tuple = env.dump_ast(source_tuple).unwrap();
assert!(dump_tuple.contains("Constant: 30"), "Nested tuple should be folded to 30. Dump:\n{}", dump_tuple);
// 2. Record-to-Tuple
let source_record = "((fn [[x y]] (+ x y)) {:a 5 :b 7})";
assert_eq!(format!("{}", env.run_script(source_record).unwrap()), "12");
let dump_record = env.dump_ast(source_record).unwrap();
assert!(dump_record.contains("Constant: 12"), "Record-to-Tuple destructuring should be folded to 12. Dump:\n{}", dump_record);
}
}