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
+18 -24
View File
@@ -209,11 +209,8 @@ impl VM {
));
}
}
Value::Object(obj) => {
return Err(format!(
"Tail call target is not callable: {}",
obj.type_name()
));
Value::Stream(_) => {
return Err("Tail call target is not callable: stream".to_string());
}
other => {
return Err(format!("Tail call target is not callable: {}", other));
@@ -411,15 +408,12 @@ impl VM {
s.series_type_name()
))
}
Value::Object(obj) => {
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
Value::Stream(s) => {
if let Some(sn) = s.as_any().downcast_ref::<StreamNode>() {
let mapped = build_map_stream(sn.inner.clone(), *field);
return Ok(Value::Object(Rc::new(mapped)));
return Ok(Value::Stream(Rc::new(mapped)));
}
Err(format!(
"Attempt to access field on non-record object: {}",
obj.type_name()
))
Err("Attempt to access field on non-record stream".to_string())
}
_ => Err(format!(
"Attempt to access field on non-record: {}",
@@ -483,7 +477,7 @@ impl VM {
self.stack.truncate(base);
match func_val {
Value::Closure(_) | Value::Object(_) | Value::Series(_) => {
Value::Closure(_) | Value::Stream(_) | Value::Series(_) => {
self.tail_call = Some((func_val, arg_vals));
return Ok(Value::Void);
}
@@ -535,15 +529,15 @@ impl VM {
k.name(),
s.series_type_name()
));
} else if let Value::Object(obj) = rec {
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
} else if let Value::Stream(s) = rec {
if let Some(sn) = s.as_any().downcast_ref::<StreamNode>() {
let mapped = build_map_stream(sn.inner.clone(), k);
return Ok(Value::Object(Rc::new(mapped)));
return Ok(Value::Stream(Rc::new(mapped)));
}
return Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
k.name(),
obj.type_name()
s.stream_type_name()
));
} else {
return Err(format!(
@@ -616,15 +610,15 @@ impl VM {
s.series_type_name()
))
}
} else if let Value::Object(obj) = rec {
if let Some(sn) = obj.as_any().downcast_ref::<StreamNode>() {
} else if let Value::Stream(s) = rec {
if let Some(sn) = s.as_any().downcast_ref::<StreamNode>() {
let mapped = build_map_stream(sn.inner.clone(), *k);
Ok(Value::Object(Rc::new(mapped)))
Ok(Value::Stream(Rc::new(mapped)))
} else {
Err(format!(
"Field accessor .{} expects a record, RecordSeries or Stream, got {}",
k.name(),
obj.type_name()
s.stream_type_name()
))
}
} else {
@@ -713,9 +707,9 @@ impl VM {
self.stack.truncate(base);
return res;
}
Value::Object(obj) => {
Value::Stream(_) => {
self.stack.truncate(base);
return Err(format!("Object is not callable: {}", obj.type_name()));
return Err("Stream is not callable".to_string());
}
_ => {
self.stack.truncate(base);
@@ -734,7 +728,7 @@ impl VM {
Value::Closure(_) => "Closure",
Value::Quote(_) => "Quote",
Value::Series(_) => "Series",
Value::Object(_) => "Object",
Value::Stream(_) => "Stream",
Value::Cell(_) => "Cell",
};
return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name));