Refactor: Use Value::Closure enum variant
Previously, compiled closures were stored as `Value::Object` and then downcasted. This commit introduces a dedicated `Value::Closure` enum variant to represent compiled closures. This improves type safety and simplifies handling of closures throughout the compiler and VM. This change involves: - Updating type definitions to use `Value::Closure`. - Adjusting downcasting logic to directly access the `Closure` struct. - Modifying `run_with_args` and `run_with_args_observed` to expect `Value::Closure` for tail-call targets. - Refining the handling of closures in the `Optimizer` and `Inliner` for better type clarity.
This commit is contained in:
@@ -3,7 +3,7 @@ use crate::ast::nodes::{
|
||||
IdentifierBinding, LambdaBinding, Node, NodeKind, UpvalueIdx, VirtualId,
|
||||
};
|
||||
use crate::ast::types::{Purity, Value};
|
||||
use crate::ast::closure::Closure;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -343,9 +343,9 @@ impl Optimizer {
|
||||
}
|
||||
}
|
||||
|
||||
if let NodeKind::Constant(Value::Object(ref obj)) = callee_opt.kind
|
||||
if let NodeKind::Constant(Value::Closure(ref closure_rc)) = callee_opt.kind
|
||||
&& path.inlining_depth < 5
|
||||
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>()
|
||||
&& let closure = closure_rc.as_ref()
|
||||
&& (closure.upvalues.is_empty()
|
||||
|| closure.function_node.ty.purity >= Purity::SideEffectFree)
|
||||
&& !closure.function_node.ty.is_recursive
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::ast::nodes::{
|
||||
Address, AnalyzedNode, IdentifierBinding, NodeKind, VirtualId,
|
||||
};
|
||||
use crate::ast::types::{Purity, Value};
|
||||
use crate::ast::closure::Closure;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
@@ -35,12 +35,8 @@ impl<'a> Inliner<'a> {
|
||||
| Value::Keyword(_)
|
||||
| Value::Record(_, _)
|
||||
| Value::DateTime(_) => true,
|
||||
Value::Object(obj) => {
|
||||
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
|
||||
closure.upvalues.is_empty() && !closure.function_node.ty.is_recursive
|
||||
} else {
|
||||
false
|
||||
}
|
||||
Value::Closure(closure_rc) => {
|
||||
closure_rc.upvalues.is_empty() && !closure_rc.function_node.ty.is_recursive
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ use crate::ast::nodes::{
|
||||
NodeKind, VirtualId,
|
||||
};
|
||||
use crate::ast::types::{Identity, Value};
|
||||
use crate::ast::closure::Closure;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
// --- PathTracker ---
|
||||
@@ -56,12 +56,10 @@ impl UsageInfo {
|
||||
self.collect(value);
|
||||
}
|
||||
NodeKind::Constant(v) => {
|
||||
if let Value::Object(obj) = v
|
||||
&& let Some(closure) = obj.as_any().downcast_ref::<Closure>()
|
||||
{
|
||||
if let Value::Closure(closure_rc) = v {
|
||||
self.used_identities
|
||||
.insert(closure.function_node.identity.clone());
|
||||
self.collect(&closure.function_node);
|
||||
.insert(closure_rc.function_node.identity.clone());
|
||||
self.collect(&closure_rc.function_node);
|
||||
}
|
||||
}
|
||||
NodeKind::Identifier { binding, .. } => {
|
||||
|
||||
+3
-12
@@ -668,13 +668,11 @@ impl Environment {
|
||||
info.positional_count,
|
||||
node.ty.stack_size,
|
||||
));
|
||||
let closure_obj: Rc<dyn Object> = closure;
|
||||
|
||||
return Rc::new(NativeFunction {
|
||||
purity: Purity::Impure,
|
||||
func: Rc::new(move |args| {
|
||||
let mut vm = VM::new(root_values.clone());
|
||||
match vm.run_with_args(closure_obj.clone(), args) {
|
||||
match vm.run_with_args(closure.clone(), args) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
||||
}
|
||||
@@ -692,12 +690,7 @@ impl Environment {
|
||||
Err(e) => panic!("Myc Runtime Error: {}", e),
|
||||
};
|
||||
|
||||
if let Value::Object(obj) = &res
|
||||
&& obj
|
||||
.as_any()
|
||||
.downcast_ref::<Closure>()
|
||||
.is_some()
|
||||
{
|
||||
if let Value::Closure(obj) = &res {
|
||||
match vm.run_with_args(obj.clone(), args) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Myc Runtime Error (Closure): {}", e),
|
||||
@@ -837,9 +830,7 @@ impl Environment {
|
||||
// 2. Execute the root closure immediately to get the actual script result.
|
||||
// All Myc scripts are wrapped in a parameterless lambda for consistency.
|
||||
let mut final_result = result;
|
||||
if let Ok(Value::Object(obj)) = &final_result
|
||||
&& let Some(_closure) = obj.as_any().downcast_ref::<Closure>()
|
||||
{
|
||||
if let Ok(Value::Closure(obj)) = &final_result {
|
||||
final_result = vm.run_with_args_observed(&mut observer, obj.clone(), &[]);
|
||||
}
|
||||
|
||||
|
||||
@@ -432,8 +432,8 @@ pub fn register(env: &Environment) {
|
||||
panic!("create-ticker expects exactly 1 argument (the condition closure)");
|
||||
}
|
||||
|
||||
let closure_obj = if let Value::Object(obj) = &args[0] {
|
||||
obj.clone()
|
||||
let closure_obj = if let Value::Closure(rc) = &args[0] {
|
||||
rc.clone()
|
||||
} else {
|
||||
panic!("create-ticker expects a closure as its argument");
|
||||
};
|
||||
|
||||
+6
-1
@@ -1,3 +1,4 @@
|
||||
use crate::ast::closure::Closure;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use std::any::Any;
|
||||
use std::cell::RefCell;
|
||||
@@ -257,7 +258,8 @@ pub enum Value {
|
||||
Record(std::sync::Arc<RecordLayout>, ValueList),
|
||||
FieldAccessor(Keyword),
|
||||
Function(Rc<NativeFunction>),
|
||||
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
||||
Closure(Rc<Closure>), // Compiled function with captured upvalues
|
||||
Object(Rc<dyn Object>), // For Series, Streams, SyntaxNodes, and custom extensions
|
||||
Cell(Rc<RefCell<Value>>), // Boxed value for captures
|
||||
}
|
||||
|
||||
@@ -277,6 +279,7 @@ impl PartialEq for Value {
|
||||
}
|
||||
(Value::FieldAccessor(a), Value::FieldAccessor(b)) => a == b,
|
||||
(Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Closure(a), Value::Closure(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b),
|
||||
(Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b),
|
||||
_ => false,
|
||||
@@ -617,6 +620,7 @@ impl Value {
|
||||
Value::Record(layout, _) => StaticType::Record(layout.clone()),
|
||||
Value::FieldAccessor(k) => StaticType::FieldAccessor(*k),
|
||||
Value::Function(_) => StaticType::Any, // Dynamic function
|
||||
Value::Closure(_) => StaticType::Object("closure"),
|
||||
Value::Object(o) => StaticType::Object(o.type_name()),
|
||||
Value::Cell(c) => c.borrow().static_type(),
|
||||
}
|
||||
@@ -660,6 +664,7 @@ impl fmt::Display for Value {
|
||||
}
|
||||
Value::FieldAccessor(k) => write!(f, ".{}", k.name()),
|
||||
Value::Function(f_meta) => write!(f, "<native fn, {:?}>", f_meta.purity),
|
||||
Value::Closure(_) => write!(f, "<closure>"),
|
||||
Value::Object(o) => write!(f, "<{:?}>", o),
|
||||
Value::Cell(c) => write!(f, "{}", c.borrow()),
|
||||
}
|
||||
|
||||
+33
-30
@@ -2,7 +2,7 @@ use crate::ast::closure::Closure;
|
||||
use crate::ast::nodes::{Address, ExecNode, IdentifierBinding, NodeKind, StackOffset};
|
||||
use crate::ast::rtl::series::{RecordSeries, SeriesView};
|
||||
use crate::ast::rtl::streams::{build_map_stream, build_pipeline_node, StreamNode};
|
||||
use crate::ast::types::{Object, PipeFn, Value};
|
||||
use crate::ast::types::{PipeFn, Value};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -88,7 +88,7 @@ pub struct VM {
|
||||
frames: Vec<CallFrame>,
|
||||
/// Side-channel for tail-call signaling. Set by `eval_internal` when a tail-call
|
||||
/// is requested; consumed by `resolve_tail_calls` and the inline TCO loop.
|
||||
tail_call: Option<(Rc<dyn Object>, Vec<Value>)>,
|
||||
tail_call: Option<(Value, Vec<Value>)>,
|
||||
}
|
||||
|
||||
impl VM {
|
||||
@@ -111,8 +111,9 @@ impl VM {
|
||||
mut result: Result<Value, String>,
|
||||
) -> Result<Value, String> {
|
||||
loop {
|
||||
if let Some((next_obj, next_args)) = self.tail_call.take() {
|
||||
if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::<Closure>() {
|
||||
if let Some((next_val, next_args)) = self.tail_call.take() {
|
||||
match next_val {
|
||||
Value::Closure(closure_rc) => {
|
||||
self.stack.clear();
|
||||
// frames should be empty here since we popped before entering the loop
|
||||
self.frames.push(CallFrame {
|
||||
@@ -136,11 +137,13 @@ impl VM {
|
||||
|
||||
result = self.eval_observed(observer, &closure.exec_node);
|
||||
self.frames.pop();
|
||||
} else if let Some(series) = next_obj.as_series() {
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Some(series) = obj.as_series() {
|
||||
if next_args.len() != 1 {
|
||||
return Err(format!(
|
||||
"{} indexer expects exactly 1 argument (the lookback index), got {}",
|
||||
next_obj.type_name(),
|
||||
obj.type_name(),
|
||||
next_args.len()
|
||||
));
|
||||
}
|
||||
@@ -148,7 +151,7 @@ impl VM {
|
||||
if *idx < 0 {
|
||||
return Err(format!(
|
||||
"{} lookback index cannot be negative: {}",
|
||||
next_obj.type_name(),
|
||||
obj.type_name(),
|
||||
idx
|
||||
));
|
||||
}
|
||||
@@ -156,16 +159,21 @@ impl VM {
|
||||
} else {
|
||||
return Err(format!(
|
||||
"{} index must be an integer, got {}",
|
||||
next_obj.type_name(),
|
||||
obj.type_name(),
|
||||
next_args[0]
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(format!(
|
||||
"Tail call target is not callable: {}",
|
||||
next_obj.type_name()
|
||||
obj.type_name()
|
||||
));
|
||||
}
|
||||
}
|
||||
other => {
|
||||
return Err(format!("Tail call target is not callable: {}", other));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
@@ -174,23 +182,18 @@ impl VM {
|
||||
|
||||
pub fn run_with_args(
|
||||
&mut self,
|
||||
closure_obj: Rc<dyn Object>,
|
||||
closure_rc: Rc<Closure>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, String> {
|
||||
self.run_with_args_observed(&mut NoOpObserver, closure_obj, args)
|
||||
self.run_with_args_observed(&mut NoOpObserver, closure_rc, args)
|
||||
}
|
||||
|
||||
pub fn run_with_args_observed<O: VMObserver>(
|
||||
&mut self,
|
||||
observer: &mut O,
|
||||
closure_obj: Rc<dyn Object>,
|
||||
closure_rc: Rc<Closure>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, String> {
|
||||
let closure_rc = closure_obj
|
||||
.into_rc_any()
|
||||
.downcast::<Closure>()
|
||||
.map_err(|_| "Object is not a closure".to_string())?;
|
||||
|
||||
self.stack.clear();
|
||||
self.frames.clear();
|
||||
|
||||
@@ -416,8 +419,8 @@ impl VM {
|
||||
let mut pipe_vm = VM::new(self.globals.clone());
|
||||
|
||||
let executor: Box<PipeFn> = match lambda_val {
|
||||
Value::Object(obj) => {
|
||||
let my_closure = obj.clone();
|
||||
Value::Closure(rc) => {
|
||||
let my_closure = rc.clone();
|
||||
Box::new(move |args: &[Value]| -> Value {
|
||||
match pipe_vm.run_with_args(my_closure.clone(), args) {
|
||||
Ok(res) => res,
|
||||
@@ -465,7 +468,7 @@ impl VM {
|
||||
info.positional_count,
|
||||
stack_size,
|
||||
);
|
||||
Ok(Value::Object(Rc::new(closure)))
|
||||
Ok(Value::Closure(Rc::new(closure)))
|
||||
}
|
||||
NodeKind::Call { callee, args } => {
|
||||
let func_val = self.eval_internal(obs, callee)?;
|
||||
@@ -481,8 +484,8 @@ impl VM {
|
||||
self.stack.truncate(base);
|
||||
|
||||
match func_val {
|
||||
Value::Object(obj) => {
|
||||
self.tail_call = Some((obj, arg_vals));
|
||||
Value::Closure(_) | Value::Object(_) => {
|
||||
self.tail_call = Some((func_val, arg_vals));
|
||||
return Ok(Value::Void);
|
||||
}
|
||||
Value::Function(f) => return Ok((f.func)(&arg_vals)),
|
||||
@@ -612,10 +615,7 @@ impl VM {
|
||||
self.stack.truncate(base);
|
||||
return res;
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Ok(closure_rc) =
|
||||
obj.clone().into_rc_any().downcast::<Closure>()
|
||||
{
|
||||
Value::Closure(closure_rc) => {
|
||||
self.frames.push(CallFrame {
|
||||
stack_base: base,
|
||||
closure: Some(closure_rc.clone()),
|
||||
@@ -662,7 +662,9 @@ impl VM {
|
||||
|
||||
self.frames.pop();
|
||||
res
|
||||
} else if let Some(series) = obj.as_series() {
|
||||
}
|
||||
Value::Object(obj) => {
|
||||
if let Some(series) = obj.as_series() {
|
||||
let arg_len = self.stack.len() - base;
|
||||
let res = if arg_len != 1 {
|
||||
Err(format!(
|
||||
@@ -707,6 +709,7 @@ impl VM {
|
||||
Value::Record(_, _) => "Record",
|
||||
Value::FieldAccessor(_) => "FieldAccessor",
|
||||
Value::Function(_) => "Function",
|
||||
Value::Closure(_) => "Closure",
|
||||
Value::Object(_) => "Object",
|
||||
Value::Cell(_) => "Cell",
|
||||
};
|
||||
@@ -714,8 +717,8 @@ impl VM {
|
||||
}
|
||||
};
|
||||
|
||||
if let Some((next_obj, next_args)) = self.tail_call.take() {
|
||||
current_func = Value::Object(next_obj);
|
||||
if let Some((next_val, next_args)) = self.tail_call.take() {
|
||||
current_func = next_val;
|
||||
self.stack.truncate(base);
|
||||
self.stack.extend(next_args);
|
||||
continue;
|
||||
@@ -736,7 +739,7 @@ impl VM {
|
||||
|
||||
let frame = self.frames.last().ok_or("No call frame for 'again'")?;
|
||||
if let Some(closure_obj) = &frame.closure {
|
||||
self.tail_call = Some((closure_obj.clone() as Rc<dyn Object>, arg_vals));
|
||||
self.tail_call = Some((Value::Closure(closure_obj.clone()), arg_vals));
|
||||
Ok(Value::Void)
|
||||
} else {
|
||||
Err("'again' called outside of a closure".to_string())
|
||||
|
||||
Reference in New Issue
Block a user