Refactor: Replace Object with StreamStorage for streams

The `Object` trait is too generic and has been causing confusion. This
commit introduces `StreamStorage` as a dedicated trait for reactive
stream types.

This change involves:

- Renaming `Object` to `StreamStorage` in relevant places.
- Updating the `Value::Object` enum variant to `Value::Stream`.
- Modifying how streams are handled in the compiler, VM, and tests to
  use the new `StreamStorage` trait.
- Adjusting example scripts and tests to reflect the change in type
  representation.
- The `StreamNode` struct now explicitly holds its `element_type`.
This commit is contained in:
2026-03-29 18:01:46 +02:00
parent a665e2c1a5
commit 10a7fcb576
9 changed files with 235 additions and 124 deletions
+53 -3
View File
@@ -43,6 +43,56 @@ fn test_pipe_infers_lambda_params_from_tuple_inputs() {
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() {
@@ -69,9 +119,9 @@ fn test_pipeline_optional_type() {
}
let val = res.unwrap();
if let Value::Object(obj) = val {
assert_eq!(obj.type_name(), "StreamNode");
if let Value::Stream(s) = val {
assert_eq!(s.stream_type_name(), "stream");
} else {
panic!("Expected an Object(StreamNode)");
panic!("Expected a Stream(StreamNode)");
}
}