Refactor PushableSeries trait

Introduce a new trait `PushableSeries` to distinguish objects that can
have values pushed into them from those that are only readable series.
This clarifies the API and allows for more precise type checking.

The `Object` trait no longer has a default `push_value` implementation.
Instead, `PushableSeries` now provides this functionality.
`SeriesMember` has also been updated to require `PushableSeries` for
RecordSeries field buffers.
This commit is contained in:
2026-03-22 19:45:50 +01:00
parent 843ee7dfed
commit ae1d94e507
2 changed files with 50 additions and 25 deletions
+36 -15
View File
@@ -5,7 +5,7 @@ use std::rc::Rc;
use std::sync::Arc;
use crate::ast::environment::Environment;
use crate::ast::types::{Keyword, Object, RecordLayout, Series, StaticType, Value};
use crate::ast::types::{Keyword, Object, PushableSeries, RecordLayout, Series, StaticType, Value};
use crate::ast::types::{Purity, Signature};
// ============================================================================
@@ -144,14 +144,20 @@ impl<T: ScalarValue> Object for ScalarSeries<T> {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
Some(self)
}
}
impl<T: ScalarValue> PushableSeries for ScalarSeries<T> {
fn push_value(&self, value: Value) {
if let Some(v) = T::from_value(value) {
self.data.borrow_mut().push(v);
}
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
}
impl<T: ScalarValue> Series for ScalarSeries<T> {
@@ -200,12 +206,18 @@ impl Object for ValueSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn push_value(&self, value: Value) {
self.data.borrow_mut().push(value);
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
Some(self)
}
}
impl PushableSeries for ValueSeries {
fn push_value(&self, value: Value) {
self.data.borrow_mut().push(value);
}
}
impl Series for ValueSeries {
@@ -224,10 +236,10 @@ impl Series for ValueSeries {
// 3. RecordSeries (SoA - Struct of Arrays)
// ============================================================================
/// Marker supertrait combining `Object` and `Series` for RecordSeries field buffers.
/// Ensures each field buffer can be stored as a typed series AND used as a dynamic object
/// (including `push_value` via the `Object` supertrait).
pub trait SeriesMember: Object + Series {}
/// Marker supertrait for RecordSeries field buffers.
/// Ensures each field buffer supports indexed access (Series), dynamic dispatch (Object),
/// and value insertion (PushableSeries).
pub trait SeriesMember: PushableSeries {}
impl SeriesMember for ScalarSeries<f64> {}
impl SeriesMember for ScalarSeries<i64> {}
@@ -321,6 +333,15 @@ impl Object for RecordSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
Some(self)
}
}
impl PushableSeries for RecordSeries {
fn push_value(&self, value: Value) {
if let Value::Record(_, values) = value {
for (i, v) in values.iter().enumerate() {
@@ -332,9 +353,6 @@ impl Object for RecordSeries {
panic!("push to RecordSeries expects a record");
}
}
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
}
impl Series for RecordSeries {
@@ -622,9 +640,12 @@ pub fn register(env: &Environment) {
let (series_val, val) = (&args[0], &args[1]);
if let Value::Object(obj) = series_val {
obj.push_value(val.clone());
if let Some(p) = obj.as_pushable_series() {
p.push_value(val.clone());
return Value::Void;
}
panic!("push expects a pushable series, got: {}", obj.type_name());
}
panic!("push expects a series object as the first argument")
},
);
+13 -9
View File
@@ -176,7 +176,7 @@ impl RecordLayout {
}
}
/// Interface for custom objects (Closures, Series, Streams)
/// Interface for custom objects (Series, Streams, SyntaxNodes, and custom extensions).
pub trait Object: fmt::Debug {
fn type_name(&self) -> &'static str;
fn as_any(&self) -> &dyn Any;
@@ -185,17 +185,21 @@ pub trait Object: fmt::Debug {
/// `Rc<T>` via `Rc::downcast::<T>()`. Requires `arbitrary_self_types` (stable since 1.81).
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any>;
/// Pushes a value into this object if it is a mutable series.
/// The default implementation panics — only series types override this.
fn push_value(&self, _val: Value) {
panic!("{} does not support push_value", self.type_name());
}
/// Optional optimization: If this object behaves like a time series (indexable lookbacks),
/// it can return a reference to its Series trait implementation.
/// If this object supports indexed lookback access, returns a `Series` reference.
fn as_series(&self) -> Option<&dyn Series> {
None
}
/// If this object supports pushing new values, returns a `PushableSeries` reference.
fn as_pushable_series(&self) -> Option<&dyn PushableSeries> {
None
}
}
/// Extension of `Object + Series` for types that also accept new values via `push_value`.
/// Only series types that are mutable (ScalarSeries, ValueSeries, RecordSeries) implement this.
pub trait PushableSeries: Object + Series {
fn push_value(&self, val: Value);
}
/// Unified interface for all series types (Local RecordSeries, SeriesViews, and future SharedSeries).