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
+37 -16
View File
@@ -5,7 +5,7 @@ use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use crate::ast::environment::Environment; 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}; 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> { fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self 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) { fn push_value(&self, value: Value) {
if let Some(v) = T::from_value(value) { if let Some(v) = T::from_value(value) {
self.data.borrow_mut().push(v); self.data.borrow_mut().push(v);
} }
} }
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
} }
impl<T: ScalarValue> Series for ScalarSeries<T> { 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> { fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self self
} }
fn push_value(&self, value: Value) {
self.data.borrow_mut().push(value);
}
fn as_series(&self) -> Option<&dyn Series> { fn as_series(&self) -> Option<&dyn Series> {
Some(self) 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 { impl Series for ValueSeries {
@@ -224,10 +236,10 @@ impl Series for ValueSeries {
// 3. RecordSeries (SoA - Struct of Arrays) // 3. RecordSeries (SoA - Struct of Arrays)
// ============================================================================ // ============================================================================
/// Marker supertrait combining `Object` and `Series` for RecordSeries field buffers. /// Marker supertrait for RecordSeries field buffers.
/// Ensures each field buffer can be stored as a typed series AND used as a dynamic object /// Ensures each field buffer supports indexed access (Series), dynamic dispatch (Object),
/// (including `push_value` via the `Object` supertrait). /// and value insertion (PushableSeries).
pub trait SeriesMember: Object + Series {} pub trait SeriesMember: PushableSeries {}
impl SeriesMember for ScalarSeries<f64> {} impl SeriesMember for ScalarSeries<f64> {}
impl SeriesMember for ScalarSeries<i64> {} impl SeriesMember for ScalarSeries<i64> {}
@@ -321,6 +333,15 @@ impl Object for RecordSeries {
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> { fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any> {
self 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) { fn push_value(&self, value: Value) {
if let Value::Record(_, values) = value { if let Value::Record(_, values) = value {
for (i, v) in values.iter().enumerate() { for (i, v) in values.iter().enumerate() {
@@ -332,9 +353,6 @@ impl Object for RecordSeries {
panic!("push to RecordSeries expects a record"); panic!("push to RecordSeries expects a record");
} }
} }
fn as_series(&self) -> Option<&dyn Series> {
Some(self)
}
} }
impl Series for RecordSeries { impl Series for RecordSeries {
@@ -622,8 +640,11 @@ pub fn register(env: &Environment) {
let (series_val, val) = (&args[0], &args[1]); let (series_val, val) = (&args[0], &args[1]);
if let Value::Object(obj) = series_val { if let Value::Object(obj) = series_val {
obj.push_value(val.clone()); if let Some(p) = obj.as_pushable_series() {
return Value::Void; 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") 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 { pub trait Object: fmt::Debug {
fn type_name(&self) -> &'static str; fn type_name(&self) -> &'static str;
fn as_any(&self) -> &dyn Any; 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). /// `Rc<T>` via `Rc::downcast::<T>()`. Requires `arbitrary_self_types` (stable since 1.81).
fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any>; fn into_rc_any(self: Rc<Self>) -> Rc<dyn Any>;
/// Pushes a value into this object if it is a mutable series. /// If this object supports indexed lookback access, returns a `Series` reference.
/// 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.
fn as_series(&self) -> Option<&dyn Series> { fn as_series(&self) -> Option<&dyn Series> {
None 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). /// Unified interface for all series types (Local RecordSeries, SeriesViews, and future SharedSeries).