use myc::ast::environment::Environment; use myc::ast::types::Value; #[test] fn test_series_api_with_limit() { let env = Environment::new(); // (series limit) — schema is inferred from push calls let source = "(series 100)"; let result = env.compile(source).into_result(); assert!(result.is_ok(), "Series creation with limit should work: {:?}", result.err()); } /// Regression test for HM type inference through nested closures. /// /// `series` is created in an outer closure, `push` happens in an inner closure. /// The pushed value type must propagate back through the TypeVar chain so that /// the compiler can inject the `:float` schema — no explicit schema given. #[test] fn test_series_infer_type_from_nested_closure() { let env = Environment::new(); let source = r#" (do (def make-acc (fn [n] (do (def s (series n)) (def total 0.0) (fn [x] (do (assign total (+ total x)) (push s x) (s 0)))))) (def acc (make-acc 5)) (acc 1.5)) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 1.5).abs() < 1e-9 => {} other => panic!("Expected Float(1.5), got {:?}", other), } } /// Regression test: TypeVar on a series used as an upvalue inside a `while` loop /// (zero-parameter lambda created by the macro) must be resolved via the global /// substitution even when `ctx.set_type` silently ignores the upvalue address. #[test] fn test_series_typevar_resolved_through_while_upvalue() { let env = Environment::new(); let source = r#" (do (def ticks (series 10)) (def i 0) (while (< i 3) (do (push ticks {:price (* i 1.0)}) (assign i (+ i 1)))) (def p (.price ticks)) (p 0) ) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 2.0).abs() < 1e-9 => {} other => panic!("Expected Float(2.0), got {:?}", other), } } // ── Numeric promotion (Int → Float widening) ──────────────────────────────── /// Push int first, then float: series element type should widen to Float. /// The int value must be readable back as a float (no truncation). #[test] fn test_series_promote_int_then_float() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (push s 1) (push s 2.5) (+ (s 0) (s 1)) ) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {} other => panic!("Expected Float(3.5), got {:?}", other), } } /// Push float first, then int: same widening, other direction. #[test] fn test_series_promote_float_then_int() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (push s 2.5) (push s 1) (+ (s 0) (s 1)) ) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {} other => panic!("Expected Float(3.5), got {:?}", other), } } /// `if` branches with int/float: the join type must be Float, not Any. /// A Float series is expected — the pushed value must be accessible as float. #[test] fn test_series_promote_if_int_float_branch() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (push s (if true 1 2.5)) (push s (if false 1 2.5)) (+ (s 0) (s 1)) ) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {} other => panic!("Expected Float(3.5), got {:?}", other), } } /// Int variable pushed into a float-inferred series must be promoted. #[test] fn test_series_promote_int_variable_into_float_series() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (def n 3) (push s 1.5) (push s n) (+ (s 0) (s 1)) ) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 4.5).abs() < 1e-9 => {} other => panic!("Expected Float(4.5), got {:?}", other), } } /// Record field promotion: pushing records where one field is int, another float. /// The field type must widen to Float. #[test] fn test_series_promote_record_field_int_float() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (push s {:v 1}) (push s {:v 2.5}) (def v (.v s)) (+ (v 0) (v 1)) ) "#; let result = env.run_script(source); match result { Ok(Value::Float(v)) if (v - 3.5).abs() < 1e-9 => {} other => panic!("Expected Float(3.5), got {:?}", other), } } /// Sanity check: Bool → Int is NOT a valid promotion (different semantic type). #[test] fn test_series_no_promotion_bool_int() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (push s true) (push s 1) (s 0) ) "#; let result = env.compile(source).into_result(); assert!(result.is_err(), "Bool->Int promotion must not be allowed"); } /// Sanity check: Float → Bool is NOT a valid promotion. #[test] fn test_series_no_promotion_float_bool() { let env = Environment::new(); let source = r#" (do (def s (series 5)) (push s 1.5) (push s true) (s 0) ) "#; let result = env.compile(source).into_result(); assert!(result.is_err(), "Float->Bool promotion must not be allowed"); } #[test] fn test_record_structural_equality_order() { let env = Environment::new(); // Known design choice: currently order dependent. let source = "(= {:a 1 :b 2} {:b 2 :a 1})"; let result = env.run_script(source); match result { Ok(Value::Bool(false)) => {}, _ => panic!("Expected false (current implementation behavior)"), } }