a5c3f3da04
This commit introduces bidirectional type inference, enabling the compiler to infer lambda parameter types based on the context of their usage. This is particularly useful for functions like `pipe`, where a lambda's signature can be deduced from the types of the streams it operates on. Key changes include: - **`extract_lambda_param_hints` function:** This new helper function in `type_checker.rs` is responsible for extracting expected parameter types for a lambda based on the callee's signature and already known argument types. - **`check_lambda_with_param_hints` function:** This function allows type-checking a lambda node using provided parameter type hints, preserving upvalue types from the enclosing scope. - **Call expression type checking:** The `check_call` function now phases its argument type checking. Non-lambda arguments are typed first, and then lambda arguments are typed using hints derived from `extract_lambda_param_hints`. - **`pipe_arg_hint_resolver`:** This new resolver for the `pipe` function's `PolymorphicFn` type allows it to provide specific type hints for its lambda argument based on the types of its stream inputs (single stream, vector of streams, or tuple of streams). - **`StaticType::PolymorphicFn` update:** The `PolymorphicFn` enum variant has been extended to include `resolve_arg_hints`, enabling functions to provide lambda parameter hints during bidirectional type inference. - **New tests:** Added tests to verify the correct inference of lambda parameters for `pipe` with vector and tuple inputs, ensuring that inner stream types are correctly propagated. Also, a test to confirm `pipe` with an optional return still produces `Stream(Float)`.
78 lines
2.7 KiB
Rust
78 lines
2.7 KiB
Rust
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> → 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 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::Object(obj) = val {
|
|
assert_eq!(obj.type_name(), "StreamNode");
|
|
} else {
|
|
panic!("Expected an Object(StreamNode)");
|
|
}
|
|
}
|