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

difftreelog

feat object destructuring defaults

Yaroslav Bolyukin2022-06-03parent: #3961a53.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -8,6 +8,9 @@
 	pub fn new() -> Self {
 		Self(Cc::new(RefCell::new(None)))
 	}
+	pub fn new_filled(v: T) -> Self {
+		Self(Cc::new(RefCell::new(Some(v))))
+	}
 	/// # Panics
 	/// If wrapper is filled already
 	pub fn fill(self, value: T) {
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -12,9 +12,11 @@
 };
 
 #[allow(clippy::too_many_lines)]
+#[allow(unused_variables)]
 pub fn destruct(
 	d: &Destruct,
 	parent: Thunk<Val>,
+	fctx: Pending<Context>,
 	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
 ) -> Result<()> {
 	match d {
@@ -89,6 +91,7 @@
 							full: full.clone(),
 							index: i,
 						})),
+						fctx.clone(),
 						new_bindings,
 					)?;
 				}
@@ -119,6 +122,7 @@
 							start: start.len(),
 							end: end.len(),
 						})),
+						fctx.clone(),
 						new_bindings,
 					)?;
 				}
@@ -151,6 +155,7 @@
 							index: i,
 							end: end.len(),
 						})),
+						fctx.clone(),
 						new_bindings,
 					)?;
 				}
@@ -189,36 +194,51 @@
 					Ok(obj)
 				}
 			}
-			let field_names: Vec<_> = fields.iter().map(|f| f.0.clone()).collect();
+			let field_names: Vec<_> = fields
+				.iter()
+				.filter(|f| f.2.is_none())
+				.map(|f| f.0.clone())
+				.collect();
 			let full = Thunk::new(tb!(DataThunk {
 				parent,
 				field_names: field_names.clone(),
 				has_rest: rest.is_some()
 			}));
 
-			for (field, d) in fields {
+			for (field, d, default) in fields {
 				#[derive(Trace)]
 				struct FieldThunk {
 					full: Thunk<ObjValue>,
 					field: IStr,
+					default: Option<(Pending<Context>, LocExpr)>,
 				}
 				impl ThunkValue for FieldThunk {
 					type Output = Val;
 
 					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
 						let full = self.full.evaluate(s.clone())?;
-						let field = full.get(s, self.field)?.expect("shape is checked");
-						Ok(field)
+						if let Some(field) = full.get(s.clone(), self.field)? {
+							Ok(field)
+						} else {
+							let (fctx, expr) = self.default.as_ref().expect("shape is checked");
+							Ok(evaluate(s, fctx.clone().unwrap(), &expr)?)
+						}
 					}
 				}
 				let value = Thunk::new(tb!(FieldThunk {
 					full: full.clone(),
-					field: field.clone()
+					field: field.clone(),
+					default: default.clone().map(|e| (fctx.clone(), e)),
 				}));
 				if let Some(d) = d {
-					destruct(d, value, new_bindings)?;
+					destruct(d, value, fctx.clone(), new_bindings)?;
 				} else {
-					destruct(&Destruct::Full(field.clone()), value, new_bindings)?;
+					destruct(
+						&Destruct::Full(field.clone()),
+						value,
+						fctx.clone(),
+						new_bindings,
+					)?;
 				}
 			}
 		}
@@ -251,10 +271,10 @@
 			}
 			let data = Thunk::new(tb!(EvaluateThunkValue {
 				name: into.name(),
-				fctx,
+				fctx: fctx.clone(),
 				expr: value.clone(),
 			}));
-			destruct(into, data, new_bindings)?;
+			destruct(into, data, fctx, new_bindings)?;
 		}
 		BindSpec::Function {
 			name,
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/parse.rs
1use gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{LocExpr, ParamsDesc};45use super::{6	arglike::ArgsLike,7	builtin::{BuiltinParam, BuiltinParamName},8};9use crate::{10	destructure::destruct,11	error::{Error::*, Result},12	evaluate_named,13	gc::GcHashMap,14	tb, throw,15	val::ThunkValue,16	Context, Pending, State, Thunk, Val,17};1819#[derive(Trace)]20struct EvaluateNamedThunk {21	ctx: Pending<Context>,22	name: IStr,23	value: LocExpr,24}2526impl ThunkValue for EvaluateNamedThunk {27	type Output = Val;28	fn get(self: Box<Self>, s: State) -> Result<Val> {29		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)30	}31}3233/// Creates correct [context](Context) for function body evaluation returning error on invalid call.34///35/// ## Parameters36/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)37/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)38/// * `params`: function parameters' definition39/// * `args`: passed function arguments40/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily41pub fn parse_function_call(42	s: State,43	ctx: Context,44	body_ctx: Context,45	params: &ParamsDesc,46	args: &dyn ArgsLike,47	tailstrict: bool,48) -> Result<Context> {49	let mut passed_args = GcHashMap::with_capacity(params.len());50	if args.unnamed_len() > params.len() {51		throw!(TooManyArgsFunctionHas(params.len()))52	}5354	let mut filled_named = 0;55	let mut filled_positionals = 0;5657	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {58		let name = params[id].0.clone();59		destruct(&name, arg, &mut passed_args)?;60		filled_positionals += 1;61		Ok(())62	})?;6364	args.named_iter(s, ctx, tailstrict, &mut |name, value| {65		// FIXME: O(n) for arg existence check66		if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {67			throw!(UnknownFunctionParameter((name as &str).to_owned()));68		}69		if passed_args.insert(name.clone(), value).is_some() {70			throw!(BindingParameterASecondTime(name.clone()));71		}72		filled_named += 1;73		Ok(())74	})?;7576	if filled_named + filled_positionals < params.len() {77		// Some args are unset, but maybe we have defaults for them78		// Default values should be created in newly created context79		let fctx = Context::new_future();80		let mut defaults =81			GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8283		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {84			if let Some(name) = param.0.name() {85				if passed_args.contains_key(&name) {86					continue;87				}88			} else if idx < filled_positionals {89				continue;90			}9192			destruct(93				&param.0,94				Thunk::new(tb!(EvaluateNamedThunk {95					ctx: fctx.clone(),96					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),97					value: param.1.clone().expect("default exists"),98				})),99				&mut defaults,100			)?;101			if param.0.name().is_some() {102				filled_named += 1;103			} else {104				filled_positionals += 1;105			}106		}107108		// Some args still weren't filled109		if filled_named + filled_positionals != params.len() {110			for param in params.iter().skip(args.unnamed_len()) {111				let mut found = false;112				args.named_names(&mut |name| {113					if Some(name) == param.0.name().as_ref() {114						found = true;115					}116				});117				if !found {118					throw!(FunctionParameterNotBoundInCall(119						param120							.0121							.clone()122							.name()123							.unwrap_or_else(|| "<destruct>".into())124					));125				}126			}127			unreachable!();128		}129130		Ok(body_ctx131			.extend(passed_args, None, None, None)132			.extend(defaults, None, None, None)133			.into_future(fctx))134	} else {135		let body_ctx = body_ctx.extend(passed_args, None, None, None);136		Ok(body_ctx)137	}138}139140/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead141///142/// ## Parameters143/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)144/// * `params`: function parameters' definition145/// * `args`: passed function arguments146/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily147pub fn parse_builtin_call(148	s: State,149	ctx: Context,150	params: &[BuiltinParam],151	args: &dyn ArgsLike,152	tailstrict: bool,153) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {154	let mut passed_args = GcHashMap::with_capacity(params.len());155	if args.unnamed_len() > params.len() {156		throw!(TooManyArgsFunctionHas(params.len()))157	}158159	let mut filled_args = 0;160161	args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {162		let name = params[id].name.clone();163		passed_args.insert(name, arg);164		filled_args += 1;165		Ok(())166	})?;167168	args.named_iter(s, ctx, tailstrict, &mut |name, arg| {169		// FIXME: O(n) for arg existence check170		let p = params171			.iter()172			.find(|p| p.name == name as &str)173			.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;174		if passed_args.insert(p.name.clone(), arg).is_some() {175			throw!(BindingParameterASecondTime(name.clone()));176		}177		filled_args += 1;178		Ok(())179	})?;180181	if filled_args < params.len() {182		for param in params.iter().filter(|p| p.has_default) {183			if passed_args.contains_key(&param.name) {184				continue;185			}186			filled_args += 1;187		}188189		// Some args still wasn't filled190		if filled_args != params.len() {191			for param in params.iter().skip(args.unnamed_len()) {192				let mut found = false;193				args.named_names(&mut |name| {194					if name as &str == &param.name as &str {195						found = true;196					}197				});198				if !found {199					throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));200				}201			}202			unreachable!();203		}204	}205	Ok(passed_args)206}207208/// Creates Context, which has all argument default values applied209/// and with unbound values causing error to be returned210pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {211	#[derive(Trace)]212	struct DependsOnUnbound(IStr);213	impl ThunkValue for DependsOnUnbound {214		type Output = Val;215		fn get(self: Box<Self>, _: State) -> Result<Val> {216			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())217		}218	}219220	let fctx = Context::new_future();221222	let mut bindings = GcHashMap::new();223224	for param in params.iter() {225		if let Some(v) = &param.1 {226			destruct(227				&param.0.clone(),228				Thunk::new(tb!(EvaluateNamedThunk {229					ctx: fctx.clone(),230					name: param.0.name().unwrap_or_else(|| "<destruct>".into()),231					value: v.clone(),232				})),233				&mut bindings,234			)?;235		} else {236			destruct(237				&param.0,238				Thunk::new(tb!(DependsOnUnbound(239					param.0.name().unwrap_or_else(|| "<destruct>".into())240				))),241				&mut bindings,242			)?;243		}244	}245246	Ok(body_ctx247		.extend(bindings, None, None, None)248		.into_future(fctx))249}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -188,7 +188,7 @@
 }
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(Debug, Clone, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, PartialEq, Trace)]
 pub enum Destruct {
 	Full(IStr),
 	#[cfg(feature = "exp-destruct")]
@@ -201,7 +201,7 @@
 	},
 	#[cfg(feature = "exp-destruct")]
 	Object {
-		fields: Vec<(IStr, Option<Destruct>)>,
+		fields: Vec<(IStr, Option<Destruct>, Option<LocExpr>)>,
 		rest: Option<DestructRest>,
 	},
 }
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -1,4 +1,4 @@
-#![allow(clippy::redundant_closure_call)]
+#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]
 
 use std::rc::Rc;
 
@@ -109,7 +109,7 @@
 			}
 		pub rule destruct_object(s: &ParserSettings) -> expr::Destruct
 			= "{" _
-				fields:(name:id() _ into:(":" _ into:destruct(s) {into})? {(name, into)})**comma()
+				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default)})**comma()
 				rest:(
 					comma() rest:destruct_rest()? {rest}
 					/ comma()? {None}