Files
RustAst/tests/pipeline.rs
T
Brummel a5c3f3da04 feat: Add bidirectional type inference for lambdas
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)`.
2026-03-23 10:51:06 +01:00

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)");
}
}