From f6c963cb413836d4a4f968a4ddd340632d14df67 Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 22 Mar 2026 19:04:32 +0100 Subject: [PATCH] Add `into_rc_any` and `push_value` to `Object` This commit introduces the `into_rc_any` and `push_value` methods to the `Object` trait. The `into_rc_any` method allows for efficient, zero-cost downcasting of `Rc` to `Rc`. This is a crucial step for enabling more flexible dynamic dispatch and type manipulation within the VM. The `push_value` method is implemented for series types (`ScalarSeries`, `ValueSeries`, `RecordSeries`) to allow pushing values directly onto them. This simplifies the `push` intrinsic function, removing the need for manual downcasting within its implementation. The default implementation for non-series objects panics, enforcing that only mutable series support this operation. Additionally, `ScalarValue` now includes a `from_value` method to facilitate converting `Value` back into concrete scalar types. This improves type safety and reduces boilerplate code when handling scalar conversions. --- src/ast/nodes.rs | 3 ++ src/ast/rtl/series.rs | 119 +++++++++++++++++++++-------------------- src/ast/rtl/streams.rs | 3 ++ src/ast/types.rs | 10 ++++ src/ast/vm.rs | 37 +++++++------ 5 files changed, 97 insertions(+), 75 deletions(-) diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index f054d33..795ade7 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -276,6 +276,9 @@ impl Object for Node { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } } pub type TypedNode = Node; diff --git a/src/ast/rtl/series.rs b/src/ast/rtl/series.rs index 0dbd0be..c0a4c38 100644 --- a/src/ast/rtl/series.rs +++ b/src/ast/rtl/series.rs @@ -77,22 +77,33 @@ impl RingBuffer { /// (No pointers/heaps like String or Record). pub trait ScalarValue: Copy + Debug + 'static { fn to_value(self) -> Value; + /// Converts a dynamic `Value` back to this scalar type, returning `None` on mismatch. + fn from_value(v: Value) -> Option; } impl ScalarValue for f64 { fn to_value(self) -> Value { Value::Float(self) } + fn from_value(v: Value) -> Option { + v.as_float() + } } impl ScalarValue for i64 { fn to_value(self) -> Value { Value::Int(self) } + fn from_value(v: Value) -> Option { + v.as_int() + } } impl ScalarValue for bool { fn to_value(self) -> Value { Value::Bool(self) } + fn from_value(v: Value) -> Option { + if let Value::Bool(b) = v { Some(b) } else { None } + } } /// A highly optimized, type-safe series for primitive values (Float, Int, Bool). @@ -130,6 +141,14 @@ impl Object for ScalarSeries { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } + 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) } @@ -178,6 +197,12 @@ impl Object for ValueSeries { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } + fn push_value(&self, value: Value) { + self.data.borrow_mut().push(value); + } fn as_series(&self) -> Option<&dyn Series> { Some(self) } @@ -199,42 +224,15 @@ impl Series for ValueSeries { // 3. RecordSeries (SoA - Struct of Arrays) // ============================================================================ -/// An interface allowing iteration over fields of a RecordSeries independently -/// of the exact type (Type Erasure for member arrays). -pub trait SeriesMember: Object + Series { - /// Pushes a dynamic Value into the series, performing the necessary downcast. - fn push_value(&self, value: Value); -} +/// 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 {} -impl SeriesMember for ScalarSeries { - fn push_value(&self, value: Value) { - if let Some(v) = value.as_float() { - self.data.borrow_mut().push(v); - } - } -} - -impl SeriesMember for ScalarSeries { - fn push_value(&self, value: Value) { - if let Some(v) = value.as_int() { - self.data.borrow_mut().push(v); - } - } -} - -impl SeriesMember for ScalarSeries { - fn push_value(&self, value: Value) { - if let Value::Bool(v) = value { - self.data.borrow_mut().push(v); - } - } -} - -impl SeriesMember for ValueSeries { - fn push_value(&self, value: Value) { - self.data.borrow_mut().push(value); - } -} +impl SeriesMember for ScalarSeries {} +impl SeriesMember for ScalarSeries {} +impl SeriesMember for ScalarSeries {} +impl SeriesMember for ValueSeries {} /// The "Struct of Arrays" implementation for Records (e.g., Ticks). /// Instead of holding a large array of Records (AoS), this series @@ -320,6 +318,20 @@ impl Object for RecordSeries { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } + fn push_value(&self, value: Value) { + if let Value::Record(_, values) = value { + for (i, v) in values.iter().enumerate() { + if let Some(f) = self.fields.get(i) { + f.borrow().push_value(v.clone()); + } + } + } else { + panic!("push to RecordSeries expects a record"); + } + } fn as_series(&self) -> Option<&dyn Series> { Some(self) } @@ -374,6 +386,9 @@ impl Object for SeriesView { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } fn as_series(&self) -> Option<&dyn Series> { Some(self) } @@ -420,6 +435,9 @@ impl Object for SharedSeries { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } fn as_series(&self) -> Option<&dyn Series> { Some(self) } @@ -458,6 +476,9 @@ impl Object for SharedValueSeries { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } fn as_series(&self) -> Option<&dyn Series> { Some(self) } @@ -499,6 +520,9 @@ impl Object for SharedRecordSeries { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } fn as_series(&self) -> Option<&dyn Series> { Some(self) } @@ -598,30 +622,7 @@ pub fn register(env: &Environment) { let (series_val, val) = (&args[0], &args[1]); if let Value::Object(obj) = series_val { - let any = obj.as_any(); - - // Polymorphic push logic - if let Some(rs) = any.downcast_ref::() { - if let Value::Record(_, values) = val { - for (i, v) in values.iter().enumerate() { - if let Some(f) = rs.fields.get(i) { - f.borrow().push_value(v.clone()); - } - } - } else { - panic!("push to RecordSeries expects a record"); - } - } else if let Some(s) = any.downcast_ref::>() { - s.push_value(val.clone()); - } else if let Some(s) = any.downcast_ref::>() { - s.push_value(val.clone()); - } else if let Some(s) = any.downcast_ref::>() { - s.push_value(val.clone()); - } else if let Some(s) = any.downcast_ref::() { - s.push_value(val.clone()); - } else { - panic!("Object is not a mutable series"); - } + obj.push_value(val.clone()); return Value::Void; } panic!("push expects a series object as the first argument") diff --git a/src/ast/rtl/streams.rs b/src/ast/rtl/streams.rs index c10d399..d45f8f6 100644 --- a/src/ast/rtl/streams.rs +++ b/src/ast/rtl/streams.rs @@ -71,6 +71,9 @@ impl Object for StreamNode { fn as_any(&self) -> &dyn std::any::Any { self } + fn into_rc_any(self: std::rc::Rc) -> std::rc::Rc { + self + } } /// The RootStream is the "Clock" and data source of the entire pipeline. diff --git a/src/ast/types.rs b/src/ast/types.rs index 468b45e..98dc2fb 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -180,6 +180,16 @@ pub trait Object: fmt::Debug { fn type_name(&self) -> &'static str; fn as_any(&self) -> &dyn Any; + /// Converts `Rc` into `Rc`, enabling zero-copy downcasting to a concrete + /// `Rc` via `Rc::downcast::()`. Requires `arbitrary_self_types` (stable since 1.81). + fn into_rc_any(self: Rc) -> Rc; + + /// 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. fn as_series(&self) -> Option<&dyn Series> { diff --git a/src/ast/vm.rs b/src/ast/vm.rs index ab024ea..a6613ba 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -48,12 +48,15 @@ impl Object for Closure { fn as_any(&self) -> &dyn Any { self } + fn into_rc_any(self: Rc) -> Rc { + self + } } #[derive(Debug)] struct CallFrame { stack_base: usize, - closure: Option>, + closure: Option>, } pub trait VMObserver { @@ -154,13 +157,14 @@ impl VM { match result { Ok(Value::TailCallRequest(payload)) => { let (next_obj, next_args) = *payload; - if let Some(closure) = next_obj.as_any().downcast_ref::() { + if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::() { self.stack.clear(); // frames should be empty here since we popped before entering the loop self.frames.push(CallFrame { stack_base: 0, - closure: Some(next_obj.clone()), + closure: Some(closure_rc.clone()), }); + let closure = closure_rc.as_ref(); if let Some(count) = closure.positional_count && next_args.len() == count as usize { @@ -227,14 +231,15 @@ impl VM { closure_obj: Rc, args: &[Value], ) -> Result { - let closure = closure_obj - .as_any() - .downcast_ref::() - .ok_or_else(|| "Object is not a closure".to_string())?; + let closure_rc = closure_obj + .into_rc_any() + .downcast::() + .map_err(|_| "Object is not a closure".to_string())?; self.stack.clear(); self.frames.clear(); + let closure = closure_rc.as_ref(); if let Some(count) = closure.positional_count && args.len() == count as usize { @@ -251,7 +256,7 @@ impl VM { self.frames.push(CallFrame { stack_base: 0, - closure: Some(closure_obj.clone()), + closure: Some(closure_rc.clone()), }); let result = self.eval_internal(observer, &closure.exec_node); @@ -652,11 +657,14 @@ impl VM { return res; } Value::Object(obj) => { - if let Some(closure) = obj.as_any().downcast_ref::() { + if let Ok(closure_rc) = + obj.clone().into_rc_any().downcast::() + { self.frames.push(CallFrame { stack_base: base, - closure: Some(obj.clone()), + closure: Some(closure_rc.clone()), }); + let closure = closure_rc.as_ref(); let unpack_res = if let Some(count) = closure.positional_count && (self.stack.len() - base) == count as usize @@ -915,8 +923,7 @@ impl VM { } Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure_obj) = &frame.closure { - let closure = closure_obj.as_any().downcast_ref::().unwrap(); + if let Some(closure) = &frame.closure { let u_idx = idx.0 as usize; if u_idx < closure.upvalues.len() { Ok(closure.upvalues[u_idx].clone()) @@ -956,8 +963,7 @@ impl VM { } Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure_obj) = &frame.closure { - let closure = closure_obj.as_any().downcast_ref::().unwrap(); + if let Some(closure) = &frame.closure { let u_idx = idx.0 as usize; if u_idx < closure.upvalues.len() { Ok(closure.upvalues[u_idx].borrow().clone()) @@ -1000,8 +1006,7 @@ impl VM { } Address::Upvalue(idx) => { let frame = self.frames.last().ok_or("No call frame")?; - if let Some(closure_obj) = &frame.closure { - let closure = closure_obj.as_any().downcast_ref::().unwrap(); + if let Some(closure) = &frame.closure { let u_idx = idx.0 as usize; if u_idx < closure.upvalues.len() { *closure.upvalues[u_idx].borrow_mut() = value;