Files
Brummel af8fb5cb7f Refactor binder to use scope depth
Introduced `scope_depth` to the `Binder` to track block nesting. This
allows for correctly determining whether a `def` binding should be
`Address::Global` (at scope depth 0) or `Address::Local`.

The `type_checker` was also updated to handle `Program` and `Block`
nodes by creating a new `TypeContext` for them.

Removed the `wrap_as_lambda` method from `Environment` as the compiler
now always produces a `Program` node, effectively handling the wrapping
at the AST level. Correspondingly, `instantiate` and
`run_script_compiled` were simplified to directly run the `Program`
node.
2026-03-31 15:22:51 +02:00

93 lines
2.6 KiB
Rust

use myc::ast::environment::Environment;
use myc::ast::types::Value;
#[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)
.into_result()
.expect("Failed to compile");
let linked = env.link(compiled);
let result = env.create_vm().run(&linked);
match result {
Ok(Value::Int(20)) => (),
Ok(val) => panic!("Expected Int(20), got {:?}", val),
Err(e) => panic!("VM Error: {}", e),
}
}
#[test]
fn test_closure_reassignment_optimization_bug() {
let env = Environment::new();
// Regression: optimizer inlined a function whose parameters were assigned to
// by an inner lambda. Such functions must NOT be inlined.
let source = "(do (def f (fn [[x y]] (fn [] (assign x (+ x y))))) ((f [1 2])))";
let res = env.run_script(source);
assert_eq!(format!("{}", res.unwrap()), "3");
}
#[test]
fn test_optimizer_upvalue_inlining_bug_repro() {
let env = Environment::new();
let source = r#"
(do
(def make-counter (fn [init]
(do
(def val init)
{
:inc (fn [] (assign val (+ val 1)))
:get (fn [] val)
})))
(def c (make-counter 10))
((.inc c))
((.get c)))
"#;
let res = env.run_script(source);
assert!(res.is_ok(), "Optimizer bug triggered: {:?}", res.err());
assert_eq!(format!("{}", res.unwrap()), "11");
}
#[test]
#[should_panic(expected = "'again' is only allowed in tail position to avoid dead code.")]
fn test_again_non_tail_panic() {
let env = Environment::new();
let source = "(do (def f (fn [x] (do (again (- x 1)) x))) (f 5))";
let _ = env.run_script(source);
}
#[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;
match val {
Ok(Value::Int(30)) => (),
_ => panic!("Expected Int(30), got {:?}", val),
}
assert!(!logs.is_empty(), "Logs should not be empty");
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");
}