use myc::ast::environment::Environment; use myc::ast::types::Value; /// Verifies that bidirectional type inference correctly propagates stream inner types /// into lambda parameters for `pipe` calls with a single stream in a vector `[src]`. /// The lambda parameter should be inferred as the stream's Record type, not `Any`. #[test] fn test_pipe_infers_lambda_param_from_vector_input() { let env = Environment::new(); let source = "(do (def src (create-random-ohlc 42 5)) (pipe [src] (fn [tick] (.close tick))) )"; let dump = env.dump_ast(source).expect("dump_ast failed"); // The lambda parameter `tick` should be inferred as the OHLC Record type, // NOT as `Any`. This validates the Vector → Stream → T hint resolution. assert!( !dump.contains("params: Tuple([Any])"), "Lambda params should NOT be Any — bidirectional inference failed.\nDump:\n{dump}" ); // The pipe call should return Stream(Float), not Stream(Any) assert!( dump.contains("Stream(Float)"), "Pipe should produce Stream(Float), not Stream(Any).\nDump:\n{dump}" ); // Also verify it runs correctly let res = env.run_script(source); assert!(res.is_ok(), "Script failed: {:?}", res.err()); } /// Verifies bidirectional inference for pipe with multiple stream inputs in a tuple. #[test] fn test_pipe_infers_lambda_params_from_tuple_inputs() { let env = Environment::new(); let source = "(do (def src1 (create-random-ohlc 42 5)) (def src2 (create-random-ohlc 99 5)) (pipe [src1 src2] (fn [t1 t2] (+ (.close t1) (.close t2)))) )"; let res = env.run_script(source); assert!(res.is_ok(), "Script failed: {:?}", res.err()); } /// Verifies that PipeHook::finalize replaces the `pipe` identifier with a /// pre-configured factory Constant — mirroring the SeriesHook pattern. /// FAILS before PipeHook is implemented (callee is still `Identifier: pipe`). #[test] fn test_pipe_callee_is_factory_after_finalize() { let env = Environment::new(); let dump = env.dump_ast("(do (def ohlc (create-random-ohlc 42 5)) (pipe ohlc (fn [bar] (.close bar))) )").expect("dump_ast failed"); assert!( !dump.contains("Identifier: pipe"), "PipeHook::finalize should replace `pipe` with a pre-configured factory Constant.\nDump:\n{dump}" ); } /// Field accessor on unknown field in pipe lambda must be a compile-time error. /// The lambda param is inferred as the concrete Record type (not Any), /// so the compiler can validate field names. #[test] fn test_pipe_field_typo_is_compile_error() { let env = Environment::new(); let valid = "(do (def ohlc (create-random-ohlc 42 5)) (pipe ohlc (fn [bar] (.close bar))))"; assert!(env.run_script(valid).is_ok(), "Valid pipe should compile"); let typo = "(do (def ohlc (create-random-ohlc 42 5)) (pipe ohlc (fn [bar] (.nonexistent bar))))"; assert!( env.run_script(typo).is_err(), "Field typo in pipe lambda must be a compile error" ); } /// A chained pipe (output feeds another pipe) must preserve concrete Stream types. #[test] fn test_pipe_chain_preserves_concrete_types() { let env = Environment::new(); let source = "(do (def ohlc (create-random-ohlc 42 5)) (def closes (pipe ohlc (fn [bar] (.close bar)))) (pipe closes (fn [c] (* c 2.0))) )"; let dump = env.dump_ast(source).expect("dump_ast failed"); assert!( dump.contains("Stream(Float)"), "Both pipe stages should yield Stream(Float).\nDump:\n{dump}" ); assert!(env.run_script(source).is_ok(), "Chained pipe failed at runtime"); } /// Verifies that pipe with Optional return (filter pattern) still produces Stream(Float). #[test] fn test_pipeline_optional_type() { let env = Environment::new(); // The lambda uses an `if` without an `else` returning a float constant. // The TypeChecker should deduce `Optional(Float)` for the lambda body, // and correctly unwrap it to `Series(Float)` for the pipeline output. let source = "(do (def src (create-random-ohlc 42 10)) (def filtered (pipe [src] (fn [tick] (if true 42.0 ) ) ) ) filtered )"; let res = env.run_script(source); if let Err(e) = &res { panic!("Script failed to compile/run: {:?}", e); } let val = res.unwrap(); if let Value::Stream(s) = val { assert_eq!(s.stream_type_name(), "stream"); } else { panic!("Expected a Stream(StreamNode)"); } }