--- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -75,36 +75,56 @@ ctx.unwrap() } - pub fn with_var(&self, name: Rc, value: Val) -> Result { + pub fn with_var(self, name: Rc, value: Val) -> Context { let mut new_bindings = HashMap::with_capacity(1); new_bindings.insert(name, resolved_lazy_val!(value)); self.extend(new_bindings, None, None, None) } pub fn extend( - &self, + self, new_bindings: HashMap, LazyVal>, new_dollar: Option, new_this: Option, new_super_obj: Option, - ) -> Result { - let dollar = new_dollar.or_else(|| self.0.dollar.clone()); - let this = new_this.or_else(|| self.0.this.clone()); - let super_obj = new_super_obj.or_else(|| self.0.super_obj.clone()); - let bindings = if new_bindings.is_empty() { - self.0.bindings.clone() - } else { - self.0.bindings.extend(new_bindings) - }; - Ok(Context(Rc::new(ContextInternals { - dollar, - this, - super_obj, - bindings, - }))) + ) -> Context { + match Rc::try_unwrap(self.0) { + Ok(mut ctx) => { + // Extended context aren't used by anything else, we can freely mutate it without cloning + if let Some(dollar) = new_dollar { + ctx.dollar = Some(dollar); + } + if let Some(this) = new_this { + ctx.this = Some(this); + } + if let Some(super_obj) = new_super_obj { + ctx.super_obj = Some(super_obj); + } + if !new_bindings.is_empty() { + ctx.bindings = ctx.bindings.extend(new_bindings); + } + Context(Rc::new(ctx)) + } + Err(ctx) => { + let dollar = new_dollar.or_else(|| ctx.dollar.clone()); + let this = new_this.or_else(|| ctx.this.clone()); + let super_obj = new_super_obj.or_else(|| ctx.super_obj.clone()); + let bindings = if new_bindings.is_empty() { + ctx.bindings.clone() + } else { + ctx.bindings.clone().extend(new_bindings) + }; + Context(Rc::new(ContextInternals { + dollar, + this, + super_obj, + bindings, + })) + } + } } pub fn extend_unbound( - &self, + self, new_bindings: HashMap, LazyBinding>, new_dollar: Option, new_this: Option, @@ -116,7 +136,7 @@ for (k, v) in new_bindings.into_iter() { new.insert(k, v.evaluate(this.clone(), super_obj.clone())?); } - self.extend(new, new_dollar, this, super_obj) + Ok(self.extend(new, new_dollar, this, super_obj)) } pub fn into_weak(self) -> WeakContext { WeakContext(Rc::downgrade(&self.0)) --- a/crates/jrsonnet-evaluator/src/evaluate.rs +++ b/crates/jrsonnet-evaluator/src/evaluate.rs @@ -209,7 +209,7 @@ for item in list.iter() { let item = item.unwrap_if_lazy()?; out.push(evaluate_comp( - context.with_var(var.clone(), item.clone())?, + context.clone().with_var(var.clone(), item.clone()), value, &specs[1..], )?); @@ -227,7 +227,7 @@ let future_this = FutureObjValue::new(); let context_creator = context_creator!( closure!(clone context, clone new_bindings, |this: Option, super_obj: Option| { - Ok(context.extend_unbound( + Ok(context.clone().extend_unbound( new_bindings.clone().unwrap(), context.dollar().clone().or_else(||this.clone()), Some(this.unwrap()), @@ -332,7 +332,7 @@ let new_bindings = FutureNewBindings::new(); let context_creator = context_creator!( closure!(clone context, clone new_bindings, |this: Option, super_obj: Option| { - Ok(context.extend_unbound( + Ok(context.clone().extend_unbound( new_bindings.clone().unwrap(), context.dollar().clone().or_else(||this.clone()), None, @@ -354,7 +354,7 @@ let key = evaluate(ctx.clone(), &obj.key)?; let value = LazyBinding::Bindable(Rc::new( closure!(clone ctx, clone obj.value, |this, _super_obj| { - Ok(LazyVal::new_resolved(evaluate(ctx.extend(HashMap::new(), None, this, None)?, &value)?)) + Ok(LazyVal::new_resolved(evaluate(ctx.clone().extend(HashMap::new(), None, this, None), &value)?)) }), )); --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -57,7 +57,7 @@ out.insert(p.0.clone(), val); } - Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?) + Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) } pub fn parse_function_call_map( @@ -106,7 +106,7 @@ out.insert(p.0.clone(), val); } - Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?) + Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) } pub(crate) fn place_args( @@ -135,7 +135,7 @@ out.insert(p.0.clone(), resolved_lazy_val!(val)); } - Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?) + Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) } #[macro_export] --- a/crates/jrsonnet-evaluator/src/map.rs +++ b/crates/jrsonnet-evaluator/src/map.rs @@ -10,12 +10,17 @@ pub struct LayeredHashMap(Rc>); impl LayeredHashMap { - pub fn extend(&self, new_layer: HashMap) -> Self { - let super_map = self.clone(); - LayeredHashMap(Rc::new(LayeredHashMapInternals { - parent: Some(super_map), - current: new_layer, - })) + pub fn extend(self, new_layer: HashMap) -> Self { + match Rc::try_unwrap(self.0) { + Ok(mut map) => { + map.current.extend(new_layer); + LayeredHashMap(Rc::new(map)) + } + Err(this) => LayeredHashMap(Rc::new(LayeredHashMapInternals { + parent: Some(LayeredHashMap(this)), + current: new_layer, + })), + } } pub fn get(&self, key: &Q) -> Option<&V>