git.delta.rocks / jrsonnet / refs/commits / e4a890bf0c31

difftreelog

perf mutate context if operating on only strong ref

Лач2020-07-21parent: #84f648e.patch.diff
in: master

4 files changed

modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- 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<str>, value: Val) -> Result<Context> {
+	pub fn with_var(self, name: Rc<str>, 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<Rc<str>, LazyVal>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
-	) -> Result<Context> {
-		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<Rc<str>, LazyBinding>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
@@ -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))
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- 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<ObjValue>, super_obj: Option<ObjValue>| {
-			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<ObjValue>, super_obj: Option<ObjValue>| {
-							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)?))
 						}),
 					));
 
modifiedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function.rs
1use crate::{error::Error::*, evaluate, lazy_val, resolved_lazy_val, throw, Context, Result, Val};2use closure::closure;3use jrsonnet_parser::{ArgsDesc, ParamsDesc};4use std::{collections::HashMap, rc::Rc};56const NO_DEFAULT_CONTEXT: &str =7	"no default context set for call with defined default parameter value";89/// Creates correct [context](Context) for function body evaluation, returning error on invalid call10///11/// * `ctx` used for passed argument expressions execution, and for body execution (if `body_ctx` is not set)12/// * `body_ctx` used for default parameter values execution, and for body execution (if set)13/// * `params` function parameters definition14/// * `args` passed function arguments15/// * `tailstruct` if true - function arguments is eager executed, otherwise - lazy16pub fn parse_function_call(17	ctx: Context,18	body_ctx: Option<Context>,19	params: &ParamsDesc,20	args: &ArgsDesc,21	tailstrict: bool,22) -> Result<Context> {23	let mut out = HashMap::new();24	let mut positioned_args = vec![None; params.0.len()];25	for (id, arg) in args.iter().enumerate() {26		let idx = if let Some(name) = &arg.0 {27			params28				.iter()29				.position(|p| *p.0 == *name)30				.ok_or_else(|| UnknownFunctionParameter(name.clone()))?31		} else {32			id33		};3435		if idx >= params.len() {36			throw!(TooManyArgsFunctionHas(params.len()));37		}38		if positioned_args[idx].is_some() {39			throw!(BindingParameterASecondTime(params[idx].0.clone()));40		}41		positioned_args[idx] = Some(arg.1.clone());42	}43	// Fill defaults44	for (id, p) in params.iter().enumerate() {45		let (ctx, expr) = if let Some(arg) = &positioned_args[id] {46			(ctx.clone(), arg)47		} else if let Some(default) = &p.1 {48			(body_ctx.clone().expect(NO_DEFAULT_CONTEXT), default)49		} else {50			throw!(FunctionParameterNotBoundInCall(p.0.clone()));51		};52		let val = if tailstrict {53			resolved_lazy_val!(evaluate(ctx, expr)?)54		} else {55			lazy_val!(closure!(clone ctx, clone expr, ||evaluate(ctx.clone(), &expr)))56		};57		out.insert(p.0.clone(), val);58	}5960	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?)61}6263pub fn parse_function_call_map(64	ctx: Context,65	body_ctx: Option<Context>,66	params: &ParamsDesc,67	args: &HashMap<Rc<str>, Val>,68	tailstrict: bool,69) -> Result<Context> {70	let mut out = HashMap::new();71	let mut positioned_args = vec![None; params.0.len()];72	for (name, val) in args.iter() {73		let idx = params74			.iter()75			.position(|p| *p.0 == **name)76			.ok_or_else(|| UnknownFunctionParameter((&name as &str).to_owned()))?;7778		if idx >= params.len() {79			throw!(TooManyArgsFunctionHas(params.len()));80		}81		if positioned_args[idx].is_some() {82			throw!(BindingParameterASecondTime(params[idx].0.clone()));83		}84		positioned_args[idx] = Some(val.clone());85	}86	// Fill defaults87	for (id, p) in params.iter().enumerate() {88		let val = if let Some(arg) = positioned_args[id].take() {89			resolved_lazy_val!(arg)90		} else if let Some(default) = &p.1 {91			if tailstrict {92				resolved_lazy_val!(evaluate(93					body_ctx.clone().expect(NO_DEFAULT_CONTEXT),94					default95				)?)96			} else {97				let body_ctx = body_ctx.clone();98				let default = default.clone();99				lazy_val!(move || {100					evaluate(body_ctx.clone().expect(NO_DEFAULT_CONTEXT), &default)101				})102			}103		} else {104			throw!(FunctionParameterNotBoundInCall(p.0.clone()));105		};106		out.insert(p.0.clone(), val);107	}108109	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?)110}111112pub(crate) fn place_args(113	ctx: Context,114	body_ctx: Option<Context>,115	params: &ParamsDesc,116	args: &[Val],117) -> Result<Context> {118	let mut out = HashMap::new();119	let mut positioned_args = vec![None; params.0.len()];120	for (id, arg) in args.iter().enumerate() {121		if id >= params.len() {122			throw!(TooManyArgsFunctionHas(params.len()));123		}124		positioned_args[id] = Some(arg);125	}126	// Fill defaults127	for (id, p) in params.iter().enumerate() {128		let val = if let Some(arg) = &positioned_args[id] {129			(*arg).clone()130		} else if let Some(default) = &p.1 {131			evaluate(ctx.clone(), default)?132		} else {133			throw!(FunctionParameterNotBoundInCall(p.0.clone()));134		};135		out.insert(p.0.clone(), resolved_lazy_val!(val));136	}137138	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?)139}140141#[macro_export]142macro_rules! parse_args {143	($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [144		$($id: expr, $name: ident $(: [$($p: path)|+] $(!! $a: path)?)?, $nt: expr);+ $(;)?145	], $handler:block) => {{146		use crate::{throw, error::Error::*};147		let args = $args;148		if args.len() > $total_args {149			throw!(TooManyArgsFunctionHas($total_args));150		}151		$(152			if args.len() <= $id {153				throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));154			}155			let $name = &args[$id];156			if $name.0.is_some() {157				if $name.0.as_ref().unwrap() != stringify!($name) {158					throw!(IntristicArgumentReorderingIsNotSupportedYet);159				}160			}161			let $name = evaluate($ctx.clone(), &$name.1)?;162			$(163				match $name {164					$($p(_))|+ => {},165					_ => throw!(TypeMismatch(166						concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"),167						$nt, $name.value_type()?168					)),169				};170				$(171					let $name = match $name {172						$a(v) => v,173						_ =>throw!(TypeMismatch(concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"), $nt, $name.value_type()?)),174					};175				)*176			)*177		)+178		($handler as crate::Result<_>)179	}};180}181182#[test]183fn test() -> Result<()> {184	use crate::val::ValType;185	use jrsonnet_parser::*;186	let state = crate::EvaluationState::default();187	let evaluator = state.with_stdlib();188	let ctx = evaluator.create_default_context()?;189	evaluator.run_in_state(|| {190		parse_args!(ctx, "test", ArgsDesc(vec![191			Arg(None, el!(Expr::Num(2.0))),192			Arg(Some("b".into()), el!(Expr::Num(1.0))),193		]), 2, [194			0, a: [Val::Num]!!Val::Num, vec![ValType::Num];195			1, b: [Val::Num]!!Val::Num, vec![ValType::Num];196		], {197			assert!((a - 2.0).abs() <= f64::EPSILON);198			assert!((b - 1.0).abs() <= f64::EPSILON);199			Ok(())200		})201		.unwrap();202		Ok(())203	})204}
after · crates/jrsonnet-evaluator/src/function.rs
1use crate::{error::Error::*, evaluate, lazy_val, resolved_lazy_val, throw, Context, Result, Val};2use closure::closure;3use jrsonnet_parser::{ArgsDesc, ParamsDesc};4use std::{collections::HashMap, rc::Rc};56const NO_DEFAULT_CONTEXT: &str =7	"no default context set for call with defined default parameter value";89/// Creates correct [context](Context) for function body evaluation, returning error on invalid call10///11/// * `ctx` used for passed argument expressions execution, and for body execution (if `body_ctx` is not set)12/// * `body_ctx` used for default parameter values execution, and for body execution (if set)13/// * `params` function parameters definition14/// * `args` passed function arguments15/// * `tailstruct` if true - function arguments is eager executed, otherwise - lazy16pub fn parse_function_call(17	ctx: Context,18	body_ctx: Option<Context>,19	params: &ParamsDesc,20	args: &ArgsDesc,21	tailstrict: bool,22) -> Result<Context> {23	let mut out = HashMap::new();24	let mut positioned_args = vec![None; params.0.len()];25	for (id, arg) in args.iter().enumerate() {26		let idx = if let Some(name) = &arg.0 {27			params28				.iter()29				.position(|p| *p.0 == *name)30				.ok_or_else(|| UnknownFunctionParameter(name.clone()))?31		} else {32			id33		};3435		if idx >= params.len() {36			throw!(TooManyArgsFunctionHas(params.len()));37		}38		if positioned_args[idx].is_some() {39			throw!(BindingParameterASecondTime(params[idx].0.clone()));40		}41		positioned_args[idx] = Some(arg.1.clone());42	}43	// Fill defaults44	for (id, p) in params.iter().enumerate() {45		let (ctx, expr) = if let Some(arg) = &positioned_args[id] {46			(ctx.clone(), arg)47		} else if let Some(default) = &p.1 {48			(body_ctx.clone().expect(NO_DEFAULT_CONTEXT), default)49		} else {50			throw!(FunctionParameterNotBoundInCall(p.0.clone()));51		};52		let val = if tailstrict {53			resolved_lazy_val!(evaluate(ctx, expr)?)54		} else {55			lazy_val!(closure!(clone ctx, clone expr, ||evaluate(ctx.clone(), &expr)))56		};57		out.insert(p.0.clone(), val);58	}5960	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))61}6263pub fn parse_function_call_map(64	ctx: Context,65	body_ctx: Option<Context>,66	params: &ParamsDesc,67	args: &HashMap<Rc<str>, Val>,68	tailstrict: bool,69) -> Result<Context> {70	let mut out = HashMap::new();71	let mut positioned_args = vec![None; params.0.len()];72	for (name, val) in args.iter() {73		let idx = params74			.iter()75			.position(|p| *p.0 == **name)76			.ok_or_else(|| UnknownFunctionParameter((&name as &str).to_owned()))?;7778		if idx >= params.len() {79			throw!(TooManyArgsFunctionHas(params.len()));80		}81		if positioned_args[idx].is_some() {82			throw!(BindingParameterASecondTime(params[idx].0.clone()));83		}84		positioned_args[idx] = Some(val.clone());85	}86	// Fill defaults87	for (id, p) in params.iter().enumerate() {88		let val = if let Some(arg) = positioned_args[id].take() {89			resolved_lazy_val!(arg)90		} else if let Some(default) = &p.1 {91			if tailstrict {92				resolved_lazy_val!(evaluate(93					body_ctx.clone().expect(NO_DEFAULT_CONTEXT),94					default95				)?)96			} else {97				let body_ctx = body_ctx.clone();98				let default = default.clone();99				lazy_val!(move || {100					evaluate(body_ctx.clone().expect(NO_DEFAULT_CONTEXT), &default)101				})102			}103		} else {104			throw!(FunctionParameterNotBoundInCall(p.0.clone()));105		};106		out.insert(p.0.clone(), val);107	}108109	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))110}111112pub(crate) fn place_args(113	ctx: Context,114	body_ctx: Option<Context>,115	params: &ParamsDesc,116	args: &[Val],117) -> Result<Context> {118	let mut out = HashMap::new();119	let mut positioned_args = vec![None; params.0.len()];120	for (id, arg) in args.iter().enumerate() {121		if id >= params.len() {122			throw!(TooManyArgsFunctionHas(params.len()));123		}124		positioned_args[id] = Some(arg);125	}126	// Fill defaults127	for (id, p) in params.iter().enumerate() {128		let val = if let Some(arg) = &positioned_args[id] {129			(*arg).clone()130		} else if let Some(default) = &p.1 {131			evaluate(ctx.clone(), default)?132		} else {133			throw!(FunctionParameterNotBoundInCall(p.0.clone()));134		};135		out.insert(p.0.clone(), resolved_lazy_val!(val));136	}137138	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None))139}140141#[macro_export]142macro_rules! parse_args {143	($ctx: expr, $fn_name: expr, $args: expr, $total_args: expr, [144		$($id: expr, $name: ident $(: [$($p: path)|+] $(!! $a: path)?)?, $nt: expr);+ $(;)?145	], $handler:block) => {{146		use crate::{throw, error::Error::*};147		let args = $args;148		if args.len() > $total_args {149			throw!(TooManyArgsFunctionHas($total_args));150		}151		$(152			if args.len() <= $id {153				throw!(FunctionParameterNotBoundInCall(stringify!($name).into()));154			}155			let $name = &args[$id];156			if $name.0.is_some() {157				if $name.0.as_ref().unwrap() != stringify!($name) {158					throw!(IntristicArgumentReorderingIsNotSupportedYet);159				}160			}161			let $name = evaluate($ctx.clone(), &$name.1)?;162			$(163				match $name {164					$($p(_))|+ => {},165					_ => throw!(TypeMismatch(166						concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"),167						$nt, $name.value_type()?168					)),169				};170				$(171					let $name = match $name {172						$a(v) => v,173						_ =>throw!(TypeMismatch(concat!($fn_name, " ", stringify!($id), "nd (", stringify!($name), ") argument"), $nt, $name.value_type()?)),174					};175				)*176			)*177		)+178		($handler as crate::Result<_>)179	}};180}181182#[test]183fn test() -> Result<()> {184	use crate::val::ValType;185	use jrsonnet_parser::*;186	let state = crate::EvaluationState::default();187	let evaluator = state.with_stdlib();188	let ctx = evaluator.create_default_context()?;189	evaluator.run_in_state(|| {190		parse_args!(ctx, "test", ArgsDesc(vec![191			Arg(None, el!(Expr::Num(2.0))),192			Arg(Some("b".into()), el!(Expr::Num(1.0))),193		]), 2, [194			0, a: [Val::Num]!!Val::Num, vec![ValType::Num];195			1, b: [Val::Num]!!Val::Num, vec![ValType::Num];196		], {197			assert!((a - 2.0).abs() <= f64::EPSILON);198			assert!((b - 1.0).abs() <= f64::EPSILON);199			Ok(())200		})201		.unwrap();202		Ok(())203	})204}
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -10,12 +10,17 @@
 pub struct LayeredHashMap<K: Hash, V>(Rc<LayeredHashMapInternals<K, V>>);
 
 impl<K: Hash + Eq, V> LayeredHashMap<K, V> {
-	pub fn extend(&self, new_layer: HashMap<K, V>) -> Self {
-		let super_map = self.clone();
-		LayeredHashMap(Rc::new(LayeredHashMapInternals {
-			parent: Some(super_map),
-			current: new_layer,
-		}))
+	pub fn extend(self, new_layer: HashMap<K, V>) -> 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<Q: ?Sized>(&self, key: &Q) -> Option<&V>