use myc::ast::environment::Environment; // --- Tests for ~ident (non-parameter unhygienic escape) --- #[test] fn test_tilde_nonparam_assign_single() { // ~x where x is not a parameter reaches the call-site variable. let env = Environment::new(); let res = env .run_script("(do (def x 0) (macro inc-x [] `(assign ~x (+ ~x 1))) (inc-x) x)") .expect("should increment x"); assert_eq!(format!("{}", res), "1"); } #[test] fn test_tilde_nonparam_assign_multiple_expansions() { // Multiple expansions of the same macro do not interfere with each other. let env = Environment::new(); let res = env .run_script( "(do (def x 0) (macro inc-x [] `(assign ~x (+ ~x 1))) (inc-x) (inc-x) (inc-x) x)", ) .expect("should increment x three times"); assert_eq!(format!("{}", res), "3"); } #[test] fn test_tilde_nonparam_def_creates_visible_binding() { // ~y as a def pattern creates a visible binding at the call site. let env = Environment::new(); let res = env .run_script("(do (macro def-y [] `(def ~y 42)) (def-y) y)") .expect("should create visible y"); assert_eq!(format!("{}", res), "42"); } #[test] fn test_tilde_nonparam_upvalue_capture() { // ~x inside a lambda captures the call-site variable as an upvalue. let env = Environment::new(); let res = env .run_script("(do (def x 10) (macro make-adder [] `(fn [n] (+ n ~x))) (def add-x (make-adder)) (add-x 5))") .expect("should capture x as upvalue"); assert_eq!(format!("{}", res), "15"); } #[test] fn test_tilde_nonparam_plain_def_stays_isolated() { // A plain template `(def x ...)` (without ~) remains hygienically isolated // even when ~x is used in the same template for references. let env = Environment::new(); let res = env .run_script("(do (def x 1) (macro double-x [] `(do (def x (* ~x 2)) ~x)) (double-x))") .expect("should return call-site x, not macro-internal x"); // ~x reads the call-site x=1; the plain `def x` creates an isolated x[ctx]=2. assert_eq!(format!("{}", res), "1"); } #[test] fn test_tilde_nonparam_missing_variable_is_error() { // ~x where x does not exist at the call site produces a compile error. let env = Environment::new(); let res = env.run_script("(do (macro inc-x [] `(assign ~x (+ ~x 1))) (inc-x))"); assert!(res.is_err(), "should fail: x is not defined at call site"); } #[test] fn test_macro_inlining_identity_collision() { let source = r#" (do (macro wrap [f] `(fn [x] (~f x))) (def add1 (fn [x] (+ x 1))) (def add2 (fn [x] (+ x 2))) (def w1 (wrap add1)) (def w2 (wrap add2)) (w1 (w2 10))) "#; // 1. Verify the result is correct let env_run = Environment::new(); let res = env_run.run_script(source).expect("Failed to run script"); assert_eq!(format!("{}", res), "13"); // 2. Verify that it was actually folded into a constant by the optimizer let env_dump = Environment::new(); let dump = env_dump.dump_ast(source).expect("Failed to dump AST"); assert!( dump.contains("Constant: 13") || dump.contains("Call"), "Macro-wrapped calls should be properly handled. Dump:\n{}", dump ); } #[test] fn test_repro_placeholder_in_template_failure() { let env = Environment::new(); let source = r#" (do (macro repeat [var limit body] `((fn [~var __limit] (if (< ~var __limit) (do ~body (again (+ ~var 1) __limit)))) 0 ~limit)) (repeat n 10 42)) "#; let res = env.run_script(source); assert!(res.is_ok(), "Expansion failed to strip placeholders: {:?}", res.err()); }