git.delta.rocks / jrsonnet / refs/commits / 7cdcae351387

difftreelog

feat simplify Thunk creation with closure syntax

Yaroslav Bolyukin2024-08-26parent: #7d331b6.patch.diff
in: master

9 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -7,7 +7,7 @@
 use super::ArrValue;
 use crate::{
 	error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, typed::Typed,
-	val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,
+	Context, Error, ObjValue, Result, Thunk, Val,
 };
 
 pub trait ArrayLike: Any + Trace + Debug {
@@ -182,23 +182,6 @@
 		Ok(Some(new_value))
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		#[derive(Trace)]
-		struct ArrayElement {
-			arr_thunk: ExprArray,
-			index: usize,
-		}
-
-		impl ThunkValue for ArrayElement {
-			type Output = Val;
-
-			fn get(self: Box<Self>) -> Result<Self::Output> {
-				self.arr_thunk
-					.get(self.index)
-					.transpose()
-					.expect("index checked")
-			}
-		}
-
 		if index >= self.len() {
 			return None;
 		}
@@ -208,9 +191,9 @@
 			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
 		};
 
-		Some(Thunk::new(ArrayElement {
-			arr_thunk: self.clone(),
-			index,
+		let arr_thunk = self.clone();
+		Some(Thunk!(move || {
+			arr_thunk.get(index).transpose().expect("index checked")
 		}))
 	}
 	fn get_cheap(&self, _index: usize) -> Option<Val> {
@@ -492,23 +475,6 @@
 		Ok(Some(new_value))
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		#[derive(Trace)]
-		struct ArrayElement<const WITH_INDEX: bool> {
-			arr_thunk: MappedArray<WITH_INDEX>,
-			index: usize,
-		}
-
-		impl<const WITH_INDEX: bool> ThunkValue for ArrayElement<WITH_INDEX> {
-			type Output = Val;
-
-			fn get(self: Box<Self>) -> Result<Self::Output> {
-				self.arr_thunk
-					.get(self.index)
-					.transpose()
-					.expect("index checked")
-			}
-		}
-
 		if index >= self.len() {
 			return None;
 		}
@@ -518,9 +484,9 @@
 			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}
 		};
 
-		Some(Thunk::new(ArrayElement {
-			arr_thunk: self.clone(),
-			index,
+		let arr_thunk = self.clone();
+		Some(Thunk!(move || {
+			arr_thunk.get(index).transpose().expect("index checked")
 		}))
 	}
 
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,13 +1,11 @@
-use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};
+use jrsonnet_parser::{BindSpec, Destruct};
 
 use crate::{
 	bail,
 	error::{ErrorKind::*, Result},
 	evaluate, evaluate_method, evaluate_named,
 	gc::GcHashMap,
-	val::ThunkValue,
 	Context, Pending, Thunk, Val,
 };
 
@@ -31,65 +29,34 @@
 		#[cfg(feature = "exp-destruct")]
 		Destruct::Array { start, rest, end } => {
 			use jrsonnet_parser::DestructRest;
-
-			use crate::arr::ArrValue;
-
-			#[derive(Trace)]
-			struct DataThunk {
-				parent: Thunk<Val>,
-				min_len: usize,
-				has_rest: bool,
-			}
-			impl ThunkValue for DataThunk {
-				type Output = ArrValue;
 
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					let v = self.parent.evaluate()?;
-					let Val::Arr(arr) = v else {
-						bail!("expected array");
-					};
-					if !self.has_rest {
-						if arr.len() != self.min_len {
-							bail!("expected {} elements, got {}", self.min_len, arr.len())
-						}
-					} else if arr.len() < self.min_len {
-						bail!(
-							"expected at least {} elements, but array was only {}",
-							self.min_len,
-							arr.len()
-						)
+			let min_len = start.len() + end.len();
+			let has_rest = rest.is_some();
+			let full = Thunk!(move || {
+				let v = parent.evaluate()?;
+				let Val::Arr(arr) = v else {
+					bail!("expected array");
+				};
+				if !has_rest {
+					if arr.len() != min_len {
+						bail!("expected {} elements, got {}", min_len, arr.len())
 					}
-					Ok(arr)
+				} else if arr.len() < min_len {
+					bail!(
+						"expected at least {} elements, but array was only {}",
+						min_len,
+						arr.len()
+					)
 				}
-			}
-
-			let full = Thunk::new(DataThunk {
-				min_len: start.len() + end.len(),
-				has_rest: rest.is_some(),
-				parent,
+				Ok(arr)
 			});
 
 			{
-				#[derive(Trace)]
-				struct BaseThunk {
-					full: Thunk<ArrValue>,
-					index: usize,
-				}
-				impl ThunkValue for BaseThunk {
-					type Output = Val;
-
-					fn get(self: Box<Self>) -> Result<Self::Output> {
-						let full = self.full.evaluate()?;
-						Ok(full.get(self.index)?.expect("length is checked"))
-					}
-				}
 				for (i, d) in start.iter().enumerate() {
+					let full = full.clone();
 					destruct(
 						d,
-						Thunk::new(BaseThunk {
-							full: full.clone(),
-							index: i,
-						}),
+						Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),
 						fctx.clone(),
 						new_bindings,
 					)?;
@@ -98,32 +65,19 @@
 
 			match rest {
 				Some(DestructRest::Keep(v)) => {
-					#[derive(Trace)]
-					struct RestThunk {
-						full: Thunk<ArrValue>,
-						start: usize,
-						end: usize,
-					}
-					impl ThunkValue for RestThunk {
-						type Output = Val;
-
-						fn get(self: Box<Self>) -> Result<Self::Output> {
-							let full = self.full.evaluate()?;
-							let to = full.len() - self.end;
+					let start = start.len();
+					let end = end.len();
+					let full = full.clone();
+					destruct(
+						&Destruct::Full(v.clone()),
+						Thunk!(move || {
+							let full = full.evaluate()?;
+							let to = full.len() - end;
 							Ok(Val::Arr(full.slice(
-								Some(self.start as i32),
+								Some(start as i32),
 								Some(to as i32),
 								None,
 							)))
-						}
-					}
-
-					destruct(
-						&Destruct::Full(v.clone()),
-						Thunk::new(RestThunk {
-							full: full.clone(),
-							start: start.len(),
-							end: end.len(),
 						}),
 						fctx.clone(),
 						new_bindings,
@@ -133,29 +87,14 @@
 			}
 
 			{
-				#[derive(Trace)]
-				struct EndThunk {
-					full: Thunk<ArrValue>,
-					index: usize,
-					end: usize,
-				}
-				impl ThunkValue for EndThunk {
-					type Output = Val;
-
-					fn get(self: Box<Self>) -> Result<Self::Output> {
-						let full = self.full.evaluate()?;
-						Ok(full
-							.get(full.len() - self.end + self.index)?
-							.expect("length is checked"))
-					}
-				}
 				for (i, d) in end.iter().enumerate() {
+					let full = full.clone();
+					let end = end.len();
 					destruct(
 						d,
-						Thunk::new(EndThunk {
-							full: full.clone(),
-							index: i,
-							end: end.len(),
+						Thunk!(move || {
+							let full = full.evaluate()?;
+							Ok(full.get(full.len() - end + i)?.expect("length is checked"))
 						}),
 						fctx.clone(),
 						new_bindings,
@@ -165,71 +104,46 @@
 		}
 		#[cfg(feature = "exp-destruct")]
 		Destruct::Object { fields, rest } => {
-			use crate::obj::ObjValue;
-
-			#[derive(Trace)]
-			struct DataThunk {
-				parent: Thunk<Val>,
-				field_names: Vec<(IStr, bool)>,
-				has_rest: bool,
-			}
-			impl ThunkValue for DataThunk {
-				type Output = ObjValue;
-
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					let v = self.parent.evaluate()?;
-					let Val::Obj(obj) = v else {
-						bail!("expected object");
-					};
-					for (field, has_default) in &self.field_names {
-						if !has_default && !obj.has_field_ex(field.clone(), true) {
-							bail!("missing field: {field}");
-						}
-					}
-					if !self.has_rest {
-						let len = obj.len();
-						if len > self.field_names.len() {
-							bail!("too many fields, and rest not found");
-						}
-					}
-					Ok(obj)
-				}
-			}
 			let field_names: Vec<_> = fields
 				.iter()
 				.map(|f| (f.0.clone(), f.2.is_some()))
 				.collect();
-			let full = Thunk::new(DataThunk {
-				parent,
-				field_names,
-				has_rest: rest.is_some(),
+			let has_rest = rest.is_some();
+			let full = Thunk!(move || {
+				let v = parent.evaluate()?;
+				let Val::Obj(obj) = v else {
+					bail!("expected object");
+				};
+				for (field, has_default) in &field_names {
+					if !has_default && !obj.has_field_ex(field.clone(), true) {
+						bail!("missing field: {field}");
+					}
+				}
+				if !has_rest {
+					let len = obj.len();
+					if len > field_names.len() {
+						bail!("too many fields, and rest not found");
+					}
+				}
+				Ok(obj)
 			});
 
 			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>) -> Result<Self::Output> {
-						let full = self.full.evaluate()?;
-						if let Some(field) = full.get(self.field)? {
+				let default = default.clone().map(|e| (fctx.clone(), e));
+				let value = {
+					let field = field.clone();
+					let full = full.clone();
+					Thunk!(move || {
+						let full = full.evaluate()?;
+						if let Some(field) = full.get(field)? {
 							Ok(field)
 						} else {
-							let (fctx, expr) = self.default.as_ref().expect("shape is checked");
+							let (fctx, expr) = default.as_ref().expect("shape is checked");
 							Ok(evaluate(fctx.clone().unwrap(), expr)?)
 						}
-					}
-				}
-				let value = Thunk::new(FieldThunk {
-					full: full.clone(),
-					field: field.clone(),
-					default: default.clone().map(|e| (fctx.clone(), e)),
-				});
+					})
+				};
+
 				if let Some(d) = d {
 					destruct(d, value, fctx.clone(), new_bindings)?;
 				} else {
@@ -253,26 +167,15 @@
 ) -> Result<()> {
 	match d {
 		BindSpec::Field { into, value } => {
-			#[derive(Trace)]
-			struct EvaluateThunkValue {
-				name: Option<IStr>,
-				fctx: Pending<Context>,
-				expr: LocExpr,
-			}
-			impl ThunkValue for EvaluateThunkValue {
-				type Output = Val;
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					self.name.map_or_else(
-						|| evaluate(self.fctx.unwrap(), &self.expr),
-						|name| evaluate_named(self.fctx.unwrap(), &self.expr, name),
-					)
-				}
-			}
-			let data = Thunk::new(EvaluateThunkValue {
-				name: into.name(),
-				fctx: fctx.clone(),
-				expr: value.clone(),
-			});
+			let name = into.name();
+			let value = value.clone();
+			let data = {
+				let fctx = fctx.clone();
+				Thunk!(move || name.map_or_else(
+					|| evaluate(fctx.unwrap(), &value),
+					|name| evaluate_named(fctx.unwrap(), &value, name),
+				))
+			};
 			destruct(into, data, fctx, new_bindings)?;
 		}
 		BindSpec::Function {
@@ -280,37 +183,15 @@
 			params,
 			value,
 		} => {
-			#[derive(Trace)]
-			struct MethodThunk {
-				fctx: Pending<Context>,
-				name: IStr,
-				params: ParamsDesc,
-				value: LocExpr,
-			}
-			impl ThunkValue for MethodThunk {
-				type Output = Val;
-
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					Ok(evaluate_method(
-						self.fctx.unwrap(),
-						self.name,
-						self.params,
-						self.value,
-					))
-				}
-			}
-
-			let old = new_bindings.insert(
-				name.clone(),
-				Thunk::new(MethodThunk {
-					fctx,
-					name: name.clone(),
-					params: params.clone(),
-					value: value.clone(),
-				}),
-			);
+			let params = params.clone();
+			let name = name.clone();
+			let value = value.clone();
+			let old = new_bindings.insert(name.clone(), {
+				let name = name.clone();
+				Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))
+			});
 			if old.is_some() {
-				bail!(DuplicateLocalVar(name.clone()))
+				bail!(DuplicateLocalVar(name))
 			}
 		}
 	}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -18,7 +18,7 @@
 	function::{CallLocation, FuncDesc, FuncVal},
 	in_frame,
 	typed::Typed,
-	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk, ThunkValue},
+	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
 	Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
 	ResultExt, Unbound, Val,
 };
@@ -139,29 +139,14 @@
 					#[cfg(feature = "exp-preserve-order")]
 					false,
 				) {
-					#[derive(Trace)]
-					struct ObjectFieldThunk {
-						obj: ObjValue,
-						field: IStr,
-					}
-					impl ThunkValue for ObjectFieldThunk {
-						type Output = Val;
-
-						fn get(self: Box<Self>) -> Result<Self::Output> {
-							self.obj.get(self.field).transpose().expect(
-								"field exists, as field name was obtained from object.fields()",
-							)
-						}
-					}
-
 					let fctx = Pending::new();
 					let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());
+					let obj = obj.clone();
 					let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
 						Thunk::evaluated(Val::string(field.clone())),
-						Thunk::new(ObjectFieldThunk {
-							field: field.clone(),
-							obj: obj.clone(),
-						}),
+						Thunk!(move || obj.get(field).transpose().expect(
+							"field exists, as field name was obtained from object.fields()",
+						)),
 					])));
 					destruct(var, value, fctx.clone(), &mut new_bindings)?;
 					let ctx = ctx
@@ -609,21 +594,8 @@
 			if items.is_empty() {
 				Val::Arr(ArrValue::empty())
 			} else if items.len() == 1 {
-				#[derive(Trace)]
-				struct ArrayElement {
-					ctx: Context,
-					item: LocExpr,
-				}
-				impl ThunkValue for ArrayElement {
-					type Output = Val;
-					fn get(self: Box<Self>) -> Result<Val> {
-						evaluate(self.ctx, &self.item)
-					}
-				}
-				Val::Arr(ArrValue::lazy(vec![Thunk::new(ArrayElement {
-					ctx,
-					item: items[0].clone(),
-				})]))
+				let item = items[0].clone();
+				Val::Arr(ArrValue::lazy(vec![Thunk!(move || evaluate(ctx, &item))]))
 			} else {
 				Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))
 			}
@@ -631,21 +603,8 @@
 		ArrComp(expr, comp_specs) => {
 			let mut out = Vec::new();
 			evaluate_comp(ctx, comp_specs, &mut |ctx| {
-				#[derive(Trace)]
-				struct EvaluateThunk {
-					ctx: Context,
-					expr: LocExpr,
-				}
-				impl ThunkValue for EvaluateThunk {
-					type Output = Val;
-					fn get(self: Box<Self>) -> Result<Val> {
-						evaluate(self.ctx, &self.expr)
-					}
-				}
-				out.push(Thunk::new(EvaluateThunk {
-					ctx,
-					expr: expr.clone(),
-				}));
+				let expr = expr.clone();
+				out.push(Thunk!(move || evaluate(ctx, &expr)));
 				Ok(())
 			})?;
 			Val::Arr(ArrValue::lazy(out))
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -90,7 +90,8 @@
 		let fctx = Context::new_future();
 		let mut defaults = GcHashMap::with_capacity(
 			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()
-				- filled_named - filled_positionals,
+				- filled_named
+				- filled_positionals,
 		);
 
 		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {
@@ -232,22 +233,6 @@
 /// Creates Context, which has all argument default values applied
 /// and with unbound values causing error to be returned
 pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {
-	#[derive(Trace)]
-	struct DependsOnUnbound(IStr, ParamsDesc);
-	impl ThunkValue for DependsOnUnbound {
-		type Output = Val;
-		fn get(self: Box<Self>) -> Result<Val> {
-			Err(FunctionParameterNotBoundInCall(
-				Some(self.0.clone()),
-				self.1
-					.iter()
-					.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))
-					.collect(),
-			)
-			.into())
-		}
-	}
-
 	let fctx = Context::new_future();
 
 	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
@@ -267,10 +252,18 @@
 		} else {
 			destruct(
 				&param.0,
-				Thunk::new(DependsOnUnbound(
-					param.0.name().unwrap_or_else(|| "<destruct>".into()),
-					params.clone(),
-				)),
+				{
+					let param_name = param.0.name().unwrap_or_else(|| "<destruct>".into());
+					let params = params.clone();
+					Thunk!(move || Err(FunctionParameterNotBoundInCall(
+						Some(param_name),
+						params
+							.iter()
+							.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))
+							.collect(),
+					)
+					.into()))
+				},
 				fctx.clone(),
 				&mut bindings,
 			)?;
modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -158,3 +158,5 @@
 		Self::new()
 	}
 }
+
+pub fn assert_trace<T: Trace>(_v: &T) {}
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -20,7 +20,7 @@
 	in_frame,
 	operator::evaluate_add_op,
 	tb,
-	val::{ArrValue, ThunkValue},
+	val::ArrValue,
 	MaybeUnbound, Result, Thunk, Unbound, Val,
 };
 
@@ -444,45 +444,16 @@
 		})
 	}
 	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {
-		#[derive(Trace)]
-		struct ThunkGet {
-			obj: ObjValue,
-			key: IStr,
-		}
-		impl ThunkValue for ThunkGet {
-			type Output = Val;
-
-			fn get(self: Box<Self>) -> Result<Self::Output> {
-				Ok(self.obj.get(self.key)?.expect("field exists"))
-			}
-		}
-
 		if !self.has_field_ex(key.clone(), true) {
 			return None;
 		}
-		Some(Thunk::new(ThunkGet {
-			obj: self.clone(),
-			key,
-		}))
+		let obj = self.clone();
+
+		Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))
 	}
 	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {
-		#[derive(Trace)]
-		struct ThunkGet {
-			obj: ObjValue,
-			key: IStr,
-		}
-		impl ThunkValue for ThunkGet {
-			type Output = Val;
-
-			fn get(self: Box<Self>) -> Result<Self::Output> {
-				self.obj.get_or_bail(self.key)
-			}
-		}
-
-		Thunk::new(ThunkGet {
-			obj: self.clone(),
-			key,
-		})
+		let obj = self.clone();
+		Thunk!(move || obj.get_or_bail(key))
 	}
 	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
 		Cc::ptr_eq(&a.0, &b.0)
@@ -733,11 +704,10 @@
 		self.value_cache
 			.borrow_mut()
 			.insert(cache_key.clone(), CacheValue::Pending);
-		let value = self.get_for_uncached(key, this).map_err(|e| {
+		let value = self.get_for_uncached(key, this).inspect_err(|e| {
 			self.value_cache
 				.borrow_mut()
 				.insert(cache_key.clone(), CacheValue::Errored(e.clone()));
-			e
 		})?;
 		self.value_cache.borrow_mut().insert(
 			cache_key,
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	cmp::Ordering,4	fmt::{self, Debug, Display},5	mem::replace,6	num::NonZeroU32,7	ops::Deref,8	rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14use jrsonnet_types::ValType;15use thiserror::Error;1617pub use crate::arr::{ArrValue, ArrayLike};18use crate::{19	bail,20	error::{Error, ErrorKind::*},21	function::FuncVal,22	gc::{GcHashMap, TraceBox},23	manifest::{ManifestFormat, ToStringFormat},24	tb,25	typed::BoundedUsize,26	ObjValue, Result, Unbound, WeakObjValue,27};2829pub trait ThunkValue: Trace {30	type Output;31	fn get(self: Box<Self>) -> Result<Self::Output>;32}3334#[derive(Trace)]35enum ThunkInner<T: Trace> {36	Computed(T),37	Errored(Error),38	Waiting(TraceBox<dyn ThunkValue<Output = T>>),39	Pending,40}4142/// Lazily evaluated value43#[allow(clippy::module_name_repetitions)]44#[derive(Clone, Trace)]45pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4647impl<T: Trace> Thunk<T> {48	pub fn evaluated(val: T) -> Self {49		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))50	}51	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {52		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))53	}54	pub fn errored(e: Error) -> Self {55		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))56	}57	pub fn result(res: Result<T, Error>) -> Self {58		match res {59			Ok(o) => Self::evaluated(o),60			Err(e) => Self::errored(e),61		}62	}63}6465impl<T> Thunk<T>66where67	T: Clone + Trace,68{69	pub fn force(&self) -> Result<()> {70		self.evaluate()?;71		Ok(())72	}7374	/// Evaluate thunk, or return cached value75	///76	/// # Errors77	///78	/// - Lazy value evaluation returned error79	/// - This method was called during inner value evaluation80	pub fn evaluate(&self) -> Result<T> {81		match &*self.0.borrow() {82			ThunkInner::Computed(v) => return Ok(v.clone()),83			ThunkInner::Errored(e) => return Err(e.clone()),84			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),85			ThunkInner::Waiting(..) => (),86		};87		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)88		else {89			unreachable!();90		};91		let new_value = match value.0.get() {92			Ok(v) => v,93			Err(e) => {94				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());95				return Err(e);96			}97		};98		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());99		Ok(new_value)100	}101}102103pub trait ThunkMapper<Input>: Trace {104	type Output;105	fn map(self, from: Input) -> Result<Self::Output>;106}107impl<Input> Thunk<Input>108where109	Input: Trace + Clone,110{111	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>112	where113		M: ThunkMapper<Input>,114		M::Output: Trace,115	{116		#[derive(Trace)]117		struct Mapped<Input: Trace, Mapper: Trace> {118			inner: Thunk<Input>,119			mapper: Mapper,120		}121		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>122		where123			Input: Trace + Clone,124			Mapper: ThunkMapper<Input>,125		{126			type Output = Mapper::Output;127128			fn get(self: Box<Self>) -> Result<Self::Output> {129				let value = self.inner.evaluate()?;130				let mapped = self.mapper.map(value)?;131				Ok(mapped)132			}133		}134135		Thunk::new(Mapped::<Input, M> {136			inner: self,137			mapper,138		})139	}140}141142impl<T: Trace> From<Result<T>> for Thunk<T> {143	fn from(value: Result<T>) -> Self {144		match value {145			Ok(o) => Self::evaluated(o),146			Err(e) => Self::errored(e),147		}148	}149}150impl<T, V: Trace> From<T> for Thunk<V>151where152	T: ThunkValue<Output = V>,153{154	fn from(value: T) -> Self {155		Self::new(value)156	}157}158159impl<T: Trace + Default> Default for Thunk<T> {160	fn default() -> Self {161		Self::evaluated(T::default())162	}163}164165type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);166167#[derive(Trace, Clone)]168pub struct CachedUnbound<I, T>169where170	I: Unbound<Bound = T>,171	T: Trace,172{173	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,174	value: I,175}176impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {177	pub fn new(value: I) -> Self {178		Self {179			cache: Cc::new(RefCell::new(GcHashMap::new())),180			value,181		}182	}183}184impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {185	type Bound = T;186	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {187		let cache_key = (188			sup.as_ref().map(|s| s.clone().downgrade()),189			this.as_ref().map(|t| t.clone().downgrade()),190		);191		{192			if let Some(t) = self.cache.borrow().get(&cache_key) {193				return Ok(t.clone());194			}195		}196		let bound = self.value.bind(sup, this)?;197198		{199			let mut cache = self.cache.borrow_mut();200			cache.insert(cache_key, bound.clone());201		}202203		Ok(bound)204	}205}206207impl<T: Debug + Trace> Debug for Thunk<T> {208	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {209		write!(f, "Lazy")210	}211}212impl<T: Trace> PartialEq for Thunk<T> {213	fn eq(&self, other: &Self) -> bool {214		Cc::ptr_eq(&self.0, &other.0)215	}216}217218/// Represents a Jsonnet value, which can be sliced or indexed (string or array).219#[allow(clippy::module_name_repetitions)]220pub enum IndexableVal {221	/// String.222	Str(IStr),223	/// Array.224	Arr(ArrValue),225}226impl IndexableVal {227	pub fn is_empty(&self) -> bool {228		match self {229			Self::Str(s) => s.is_empty(),230			Self::Arr(s) => s.is_empty(),231		}232	}233234	pub fn to_array(self) -> ArrValue {235		match self {236			Self::Str(s) => ArrValue::chars(s.chars()),237			Self::Arr(arr) => arr,238		}239	}240	/// Slice the value.241	///242	/// # Implementation243	///244	/// For strings, will create a copy of specified interval.245	///246	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.247	pub fn slice(248		self,249		index: Option<i32>,250		end: Option<i32>,251		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,252	) -> Result<Self> {253		match &self {254			Self::Str(s) => {255				let mut computed_len = None;256				let mut get_len = || {257					computed_len.map_or_else(258						|| {259							let len = s.chars().count();260							let _ = computed_len.insert(len);261							len262						},263						|len| len,264					)265				};266				let mut get_idx = |pos: Option<i32>, default| {267					match pos {268						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),269						// No need to clamp, as iterator interface is used270						Some(v) => v as usize,271						None => default,272					}273				};274275				let index = get_idx(index, 0);276				let end = get_idx(end, usize::MAX);277				let step = step.as_deref().copied().unwrap_or(1);278279				if index >= end {280					return Ok(Self::Str("".into()));281				}282283				Ok(Self::Str(284					(s.chars()285						.skip(index)286						.take(end - index)287						.step_by(step)288						.collect::<String>())289					.into(),290				))291			}292			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(293				index,294				end,295				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),296			))),297		}298	}299}300301#[derive(Debug, Clone, Trace)]302pub enum StrValue {303	Flat(IStr),304	Tree(Rc<(StrValue, StrValue, usize)>),305}306impl StrValue {307	pub fn concat(a: Self, b: Self) -> Self {308		// TODO: benchmark for an optimal value, currently just a arbitrary choice309		const STRING_EXTEND_THRESHOLD: usize = 100;310311		if a.is_empty() {312			b313		} else if b.is_empty() {314			a315		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {316			Self::Flat(format!("{a}{b}").into())317		} else {318			let len = a.len() + b.len();319			Self::Tree(Rc::new((a, b, len)))320		}321	}322	pub fn into_flat(self) -> IStr {323		#[cold]324		fn write_buf(s: &StrValue, out: &mut String) {325			match s {326				StrValue::Flat(f) => out.push_str(f),327				StrValue::Tree(t) => {328					write_buf(&t.0, out);329					write_buf(&t.1, out);330				}331			}332		}333		match self {334			Self::Flat(f) => f,335			Self::Tree(_) => {336				let mut buf = String::with_capacity(self.len());337				write_buf(&self, &mut buf);338				buf.into()339			}340		}341	}342	pub fn len(&self) -> usize {343		match self {344			Self::Flat(v) => v.len(),345			Self::Tree(t) => t.2,346		}347	}348	pub fn is_empty(&self) -> bool {349		match self {350			Self::Flat(v) => v.is_empty(),351			// Can't create non-flat empty string352			Self::Tree(_) => false,353		}354	}355}356impl<T> From<T> for StrValue357where358	IStr: From<T>,359{360	fn from(value: T) -> Self {361		Self::Flat(IStr::from(value))362	}363}364impl Display for StrValue {365	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {366		match self {367			Self::Flat(v) => write!(f, "{v}"),368			Self::Tree(t) => {369				write!(f, "{}", t.0)?;370				write!(f, "{}", t.1)371			}372		}373	}374}375impl PartialEq for StrValue {376	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.377	#[allow(clippy::unconditional_recursion)]378	fn eq(&self, other: &Self) -> bool {379		let a = self.clone().into_flat();380		let b = other.clone().into_flat();381		a == b382	}383}384impl Eq for StrValue {}385impl PartialOrd for StrValue {386	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {387		Some(self.cmp(other))388	}389}390impl Ord for StrValue {391	fn cmp(&self, other: &Self) -> Ordering {392		let a = self.clone().into_flat();393		let b = other.clone().into_flat();394		a.cmp(&b)395	}396}397398/// Represents jsonnet number399/// Jsonnet numbers are finite f64, with NaNs disallowed400#[derive(Trace, Clone, Copy, Derivative)]401#[derivative(Debug = "transparent")]402#[repr(transparent)]403pub struct NumValue(f64);404impl NumValue {405	/// Creates a [`NumValue`], if value is finite and not NaN406	pub fn new(v: f64) -> Option<Self> {407		if !v.is_finite() {408			return None;409		}410		Some(Self(v))411	}412	#[inline]413	pub const fn get(&self) -> f64 {414		self.0415	}416}417impl PartialEq for NumValue {418	fn eq(&self, other: &Self) -> bool {419		self.0 == other.0420	}421}422impl Eq for NumValue {}423impl Ord for NumValue {424	#[inline]425	fn cmp(&self, other: &Self) -> Ordering {426		// Can't use `total_cmp`: its behavior for `-0` and `0`427		// is not following wanted.428		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }429	}430}431impl PartialOrd for NumValue {432	#[inline]433	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {434		Some(self.cmp(other))435	}436}437impl Display for NumValue {438	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {439		Display::fmt(&self.0, f)440	}441}442impl Deref for NumValue {443	type Target = f64;444445	#[inline]446	fn deref(&self) -> &Self::Target {447		&self.0448	}449}450macro_rules! impl_num {451	($($ty:ty),+) => {$(452		impl From<$ty> for NumValue {453			#[inline]454			fn from(value: $ty) -> Self {455				Self(value.into())456			}457		}458	)+};459}460impl_num!(i8, u8, i16, u16, i32, u32);461462#[derive(Clone, Copy, Debug, Error, Trace)]463pub enum ConvertNumValueError {464	#[error("overflow")]465	Overflow,466	#[error("underflow")]467	Underflow,468	#[error("non-finite")]469	NonFinite,470}471impl From<ConvertNumValueError> for Error {472	fn from(e: ConvertNumValueError) -> Self {473		Self::new(e.into())474	}475}476477macro_rules! impl_try_num {478	($($ty:ty),+) => {$(479		impl TryFrom<$ty> for NumValue {480			type Error = ConvertNumValueError;481			#[inline]482			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {483				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};484				let value = value as f64;485				if value < MIN_SAFE_INTEGER {486					return Err(ConvertNumValueError::Underflow)487				} else if value > MAX_SAFE_INTEGER {488					return Err(ConvertNumValueError::Overflow)489				}490				// Number is finite.491				Ok(Self(value))492			}493		}494	)+};495}496impl_try_num!(usize, isize, i64, u64);497498impl TryFrom<f64> for NumValue {499	type Error = ConvertNumValueError;500501	#[inline]502	fn try_from(value: f64) -> Result<Self, Self::Error> {503		Self::new(value).ok_or(ConvertNumValueError::NonFinite)504	}505}506impl TryFrom<f32> for NumValue {507	type Error = ConvertNumValueError;508509	#[inline]510	fn try_from(value: f32) -> Result<Self, Self::Error> {511		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)512	}513}514515/// Represents any valid Jsonnet value.516#[derive(Debug, Clone, Trace, Default)]517pub enum Val {518	/// Represents a Jsonnet boolean.519	Bool(bool),520	/// Represents a Jsonnet null value.521	#[default]522	Null,523	/// Represents a Jsonnet string.524	Str(StrValue),525	/// Represents a Jsonnet number.526	/// Should be finite, and not NaN527	/// This restriction isn't enforced by enum, as enum field can't be marked as private528	Num(NumValue),529	/// Experimental bigint530	#[cfg(feature = "exp-bigint")]531	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),532	/// Represents a Jsonnet array.533	Arr(ArrValue),534	/// Represents a Jsonnet object.535	Obj(ObjValue),536	/// Represents a Jsonnet function.537	Func(FuncVal),538}539540#[cfg(target_pointer_width = "64")]541static_assertions::assert_eq_size!(Val, [u8; 24]);542543impl From<IndexableVal> for Val {544	fn from(v: IndexableVal) -> Self {545		match v {546			IndexableVal::Str(s) => Self::string(s),547			IndexableVal::Arr(a) => Self::Arr(a),548		}549	}550}551552impl Val {553	pub const fn as_bool(&self) -> Option<bool> {554		match self {555			Self::Bool(v) => Some(*v),556			_ => None,557		}558	}559	pub const fn as_null(&self) -> Option<()> {560		match self {561			Self::Null => Some(()),562			_ => None,563		}564	}565	pub fn as_str(&self) -> Option<IStr> {566		match self {567			Self::Str(s) => Some(s.clone().into_flat()),568			_ => None,569		}570	}571	pub const fn as_num(&self) -> Option<f64> {572		match self {573			Self::Num(n) => Some(n.get()),574			_ => None,575		}576	}577	pub fn as_arr(&self) -> Option<ArrValue> {578		match self {579			Self::Arr(a) => Some(a.clone()),580			_ => None,581		}582	}583	pub fn as_obj(&self) -> Option<ObjValue> {584		match self {585			Self::Obj(o) => Some(o.clone()),586			_ => None,587		}588	}589	pub fn as_func(&self) -> Option<FuncVal> {590		match self {591			Self::Func(f) => Some(f.clone()),592			_ => None,593		}594	}595596	pub const fn value_type(&self) -> ValType {597		match self {598			Self::Str(..) => ValType::Str,599			Self::Num(..) => ValType::Num,600			#[cfg(feature = "exp-bigint")]601			Self::BigInt(..) => ValType::BigInt,602			Self::Arr(..) => ValType::Arr,603			Self::Obj(..) => ValType::Obj,604			Self::Bool(_) => ValType::Bool,605			Self::Null => ValType::Null,606			Self::Func(..) => ValType::Func,607		}608	}609610	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {611		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {612			manifest.manifest(val.clone())613		}614		manifest_dyn(self, &format)615	}616617	pub fn to_string(&self) -> Result<IStr> {618		Ok(match self {619			Self::Bool(true) => "true".into(),620			Self::Bool(false) => "false".into(),621			Self::Null => "null".into(),622			Self::Str(s) => s.clone().into_flat(),623			_ => self.manifest(ToStringFormat).map(IStr::from)?,624		})625	}626627	pub fn into_indexable(self) -> Result<IndexableVal> {628		Ok(match self {629			Self::Str(s) => IndexableVal::Str(s.into_flat()),630			Self::Arr(arr) => IndexableVal::Arr(arr),631			_ => bail!(ValueIsNotIndexable(self.value_type())),632		})633	}634635	pub fn function(function: impl Into<FuncVal>) -> Self {636		Self::Func(function.into())637	}638	pub fn string(string: impl Into<StrValue>) -> Self {639		Self::Str(string.into())640	}641	pub fn num(num: impl Into<NumValue>) -> Self {642		Self::Num(num.into())643	}644	pub fn try_num<V, E>(num: V) -> Result<Self, E>645	where646		NumValue: TryFrom<V, Error = E>,647	{648		Ok(Self::Num(num.try_into()?))649	}650}651652impl From<IStr> for Val {653	fn from(value: IStr) -> Self {654		Self::string(value)655	}656}657impl From<String> for Val {658	fn from(value: String) -> Self {659		Self::string(value)660	}661}662impl From<&str> for Val {663	fn from(value: &str) -> Self {664		Self::string(value)665	}666}667impl From<ObjValue> for Val {668	fn from(value: ObjValue) -> Self {669		Self::Obj(value)670	}671}672673const fn is_function_like(val: &Val) -> bool {674	matches!(val, Val::Func(_))675}676677/// Native implementation of `std.primitiveEquals`678pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {679	Ok(match (val_a, val_b) {680		(Val::Bool(a), Val::Bool(b)) => a == b,681		(Val::Null, Val::Null) => true,682		(Val::Str(a), Val::Str(b)) => a == b,683		(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,684		#[cfg(feature = "exp-bigint")]685		(Val::BigInt(a), Val::BigInt(b)) => a == b,686		(Val::Arr(_), Val::Arr(_)) => {687			bail!("primitiveEquals operates on primitive types, got array")688		}689		(Val::Obj(_), Val::Obj(_)) => {690			bail!("primitiveEquals operates on primitive types, got object")691		}692		(a, b) if is_function_like(a) && is_function_like(b) => {693			bail!("cannot test equality of functions")694		}695		(_, _) => false,696	})697}698699/// Native implementation of `std.equals`700pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {701	if val_a.value_type() != val_b.value_type() {702		return Ok(false);703	}704	match (val_a, val_b) {705		(Val::Arr(a), Val::Arr(b)) => {706			if ArrValue::ptr_eq(a, b) {707				return Ok(true);708			}709			if a.len() != b.len() {710				return Ok(false);711			}712			for (a, b) in a.iter().zip(b.iter()) {713				if !equals(&a?, &b?)? {714					return Ok(false);715				}716			}717			Ok(true)718		}719		(Val::Obj(a), Val::Obj(b)) => {720			if ObjValue::ptr_eq(a, b) {721				return Ok(true);722			}723			let fields = a.fields(724				#[cfg(feature = "exp-preserve-order")]725				false,726			);727			if fields728				!= b.fields(729					#[cfg(feature = "exp-preserve-order")]730					false,731				) {732				return Ok(false);733			}734			for field in fields {735				if !equals(736					&a.get(field.clone())?.expect("field exists"),737					&b.get(field)?.expect("field exists"),738				)? {739					return Ok(false);740				}741			}742			Ok(true)743		}744		(a, b) => Ok(primitive_equals(a, b)?),745	}746}
after · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	cmp::Ordering,4	fmt::{self, Debug, Display},5	mem::replace,6	num::NonZeroU32,7	ops::Deref,8	rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use thiserror::Error;1718pub use crate::arr::{ArrValue, ArrayLike};19use crate::{20	bail,21	error::{Error, ErrorKind::*},22	function::FuncVal,23	gc::{GcHashMap, TraceBox},24	manifest::{ManifestFormat, ToStringFormat},25	tb,26	typed::BoundedUsize,27	ObjValue, Result, Unbound, WeakObjValue,28};2930pub trait ThunkValue: Trace {31	type Output;32	fn get(self: Box<Self>) -> Result<Self::Output>;33}3435#[derive(Trace)]36pub struct ThunkValueClosure<D: Trace, O: 'static> {37	env: D,38	// Carries no data, as it is not a real closure, all the39	// captured environment is stored in `env` field.40	#[trace(skip)]41	closure: fn(D) -> Result<O>,42}43impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {44	pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {45		Self { env, closure }46	}47}48impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {49	type Output = O;5051	fn get(self: Box<Self>) -> Result<Self::Output> {52		(self.closure)(self.env)53	}54}5556#[derive(Trace)]57enum ThunkInner<T: Trace> {58	Computed(T),59	Errored(Error),60	Waiting(TraceBox<dyn ThunkValue<Output = T>>),61	Pending,62}6364/// Lazily evaluated value65#[allow(clippy::module_name_repetitions)]66#[derive(Clone, Trace)]67pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);6869impl<T: Trace> Thunk<T> {70	pub fn evaluated(val: T) -> Self {71		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))72	}73	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {74		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))75	}76	pub fn errored(e: Error) -> Self {77		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))78	}79	pub fn result(res: Result<T, Error>) -> Self {80		match res {81			Ok(o) => Self::evaluated(o),82			Err(e) => Self::errored(e),83		}84	}85}8687impl<T> Thunk<T>88where89	T: Clone + Trace,90{91	pub fn force(&self) -> Result<()> {92		self.evaluate()?;93		Ok(())94	}9596	/// Evaluate thunk, or return cached value97	///98	/// # Errors99	///100	/// - Lazy value evaluation returned error101	/// - This method was called during inner value evaluation102	pub fn evaluate(&self) -> Result<T> {103		match &*self.0.borrow() {104			ThunkInner::Computed(v) => return Ok(v.clone()),105			ThunkInner::Errored(e) => return Err(e.clone()),106			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),107			ThunkInner::Waiting(..) => (),108		};109		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)110		else {111			unreachable!();112		};113		let new_value = match value.0.get() {114			Ok(v) => v,115			Err(e) => {116				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());117				return Err(e);118			}119		};120		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());121		Ok(new_value)122	}123}124125pub trait ThunkMapper<Input>: Trace {126	type Output;127	fn map(self, from: Input) -> Result<Self::Output>;128}129impl<Input> Thunk<Input>130where131	Input: Trace + Clone,132{133	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>134	where135		M: ThunkMapper<Input>,136		M::Output: Trace,137	{138		let inner = self;139		Thunk!(move || {140			let value = inner.evaluate()?;141			let mapped = mapper.map(value)?;142			Ok(mapped)143		})144	}145}146147impl<T: Trace> From<Result<T>> for Thunk<T> {148	fn from(value: Result<T>) -> Self {149		match value {150			Ok(o) => Self::evaluated(o),151			Err(e) => Self::errored(e),152		}153	}154}155impl<T, V: Trace> From<T> for Thunk<V>156where157	T: ThunkValue<Output = V>,158{159	fn from(value: T) -> Self {160		Self::new(value)161	}162}163164impl<T: Trace + Default> Default for Thunk<T> {165	fn default() -> Self {166		Self::evaluated(T::default())167	}168}169170type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);171172#[derive(Trace, Clone)]173pub struct CachedUnbound<I, T>174where175	I: Unbound<Bound = T>,176	T: Trace,177{178	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,179	value: I,180}181impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {182	pub fn new(value: I) -> Self {183		Self {184			cache: Cc::new(RefCell::new(GcHashMap::new())),185			value,186		}187	}188}189impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {190	type Bound = T;191	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {192		let cache_key = (193			sup.as_ref().map(|s| s.clone().downgrade()),194			this.as_ref().map(|t| t.clone().downgrade()),195		);196		{197			if let Some(t) = self.cache.borrow().get(&cache_key) {198				return Ok(t.clone());199			}200		}201		let bound = self.value.bind(sup, this)?;202203		{204			let mut cache = self.cache.borrow_mut();205			cache.insert(cache_key, bound.clone());206		}207208		Ok(bound)209	}210}211212impl<T: Debug + Trace> Debug for Thunk<T> {213	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {214		write!(f, "Lazy")215	}216}217impl<T: Trace> PartialEq for Thunk<T> {218	fn eq(&self, other: &Self) -> bool {219		Cc::ptr_eq(&self.0, &other.0)220	}221}222223/// Represents a Jsonnet value, which can be sliced or indexed (string or array).224#[allow(clippy::module_name_repetitions)]225pub enum IndexableVal {226	/// String.227	Str(IStr),228	/// Array.229	Arr(ArrValue),230}231impl IndexableVal {232	pub fn is_empty(&self) -> bool {233		match self {234			Self::Str(s) => s.is_empty(),235			Self::Arr(s) => s.is_empty(),236		}237	}238239	pub fn to_array(self) -> ArrValue {240		match self {241			Self::Str(s) => ArrValue::chars(s.chars()),242			Self::Arr(arr) => arr,243		}244	}245	/// Slice the value.246	///247	/// # Implementation248	///249	/// For strings, will create a copy of specified interval.250	///251	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.252	pub fn slice(253		self,254		index: Option<i32>,255		end: Option<i32>,256		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,257	) -> Result<Self> {258		match &self {259			Self::Str(s) => {260				let mut computed_len = None;261				let mut get_len = || {262					computed_len.map_or_else(263						|| {264							let len = s.chars().count();265							let _ = computed_len.insert(len);266							len267						},268						|len| len,269					)270				};271				let mut get_idx = |pos: Option<i32>, default| {272					match pos {273						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),274						// No need to clamp, as iterator interface is used275						Some(v) => v as usize,276						None => default,277					}278				};279280				let index = get_idx(index, 0);281				let end = get_idx(end, usize::MAX);282				let step = step.as_deref().copied().unwrap_or(1);283284				if index >= end {285					return Ok(Self::Str("".into()));286				}287288				Ok(Self::Str(289					(s.chars()290						.skip(index)291						.take(end - index)292						.step_by(step)293						.collect::<String>())294					.into(),295				))296			}297			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(298				index,299				end,300				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),301			))),302		}303	}304}305306#[derive(Debug, Clone, Trace)]307pub enum StrValue {308	Flat(IStr),309	Tree(Rc<(StrValue, StrValue, usize)>),310}311impl StrValue {312	pub fn concat(a: Self, b: Self) -> Self {313		// TODO: benchmark for an optimal value, currently just a arbitrary choice314		const STRING_EXTEND_THRESHOLD: usize = 100;315316		if a.is_empty() {317			b318		} else if b.is_empty() {319			a320		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {321			Self::Flat(format!("{a}{b}").into())322		} else {323			let len = a.len() + b.len();324			Self::Tree(Rc::new((a, b, len)))325		}326	}327	pub fn into_flat(self) -> IStr {328		#[cold]329		fn write_buf(s: &StrValue, out: &mut String) {330			match s {331				StrValue::Flat(f) => out.push_str(f),332				StrValue::Tree(t) => {333					write_buf(&t.0, out);334					write_buf(&t.1, out);335				}336			}337		}338		match self {339			Self::Flat(f) => f,340			Self::Tree(_) => {341				let mut buf = String::with_capacity(self.len());342				write_buf(&self, &mut buf);343				buf.into()344			}345		}346	}347	pub fn len(&self) -> usize {348		match self {349			Self::Flat(v) => v.len(),350			Self::Tree(t) => t.2,351		}352	}353	pub fn is_empty(&self) -> bool {354		match self {355			Self::Flat(v) => v.is_empty(),356			// Can't create non-flat empty string357			Self::Tree(_) => false,358		}359	}360}361impl<T> From<T> for StrValue362where363	IStr: From<T>,364{365	fn from(value: T) -> Self {366		Self::Flat(IStr::from(value))367	}368}369impl Display for StrValue {370	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {371		match self {372			Self::Flat(v) => write!(f, "{v}"),373			Self::Tree(t) => {374				write!(f, "{}", t.0)?;375				write!(f, "{}", t.1)376			}377		}378	}379}380impl PartialEq for StrValue {381	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.382	#[allow(clippy::unconditional_recursion)]383	fn eq(&self, other: &Self) -> bool {384		let a = self.clone().into_flat();385		let b = other.clone().into_flat();386		a == b387	}388}389impl Eq for StrValue {}390impl PartialOrd for StrValue {391	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {392		Some(self.cmp(other))393	}394}395impl Ord for StrValue {396	fn cmp(&self, other: &Self) -> Ordering {397		let a = self.clone().into_flat();398		let b = other.clone().into_flat();399		a.cmp(&b)400	}401}402403/// Represents jsonnet number404/// Jsonnet numbers are finite f64, with NaNs disallowed405#[derive(Trace, Clone, Copy, Derivative)]406#[derivative(Debug = "transparent")]407#[repr(transparent)]408pub struct NumValue(f64);409impl NumValue {410	/// Creates a [`NumValue`], if value is finite and not NaN411	pub fn new(v: f64) -> Option<Self> {412		if !v.is_finite() {413			return None;414		}415		Some(Self(v))416	}417	#[inline]418	pub const fn get(&self) -> f64 {419		self.0420	}421}422impl PartialEq for NumValue {423	fn eq(&self, other: &Self) -> bool {424		self.0 == other.0425	}426}427impl Eq for NumValue {}428impl Ord for NumValue {429	#[inline]430	fn cmp(&self, other: &Self) -> Ordering {431		// Can't use `total_cmp`: its behavior for `-0` and `0`432		// is not following wanted.433		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }434	}435}436impl PartialOrd for NumValue {437	#[inline]438	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {439		Some(self.cmp(other))440	}441}442impl Display for NumValue {443	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {444		Display::fmt(&self.0, f)445	}446}447impl Deref for NumValue {448	type Target = f64;449450	#[inline]451	fn deref(&self) -> &Self::Target {452		&self.0453	}454}455macro_rules! impl_num {456	($($ty:ty),+) => {$(457		impl From<$ty> for NumValue {458			#[inline]459			fn from(value: $ty) -> Self {460				Self(value.into())461			}462		}463	)+};464}465impl_num!(i8, u8, i16, u16, i32, u32);466467#[derive(Clone, Copy, Debug, Error, Trace)]468pub enum ConvertNumValueError {469	#[error("overflow")]470	Overflow,471	#[error("underflow")]472	Underflow,473	#[error("non-finite")]474	NonFinite,475}476impl From<ConvertNumValueError> for Error {477	fn from(e: ConvertNumValueError) -> Self {478		Self::new(e.into())479	}480}481482macro_rules! impl_try_num {483	($($ty:ty),+) => {$(484		impl TryFrom<$ty> for NumValue {485			type Error = ConvertNumValueError;486			#[inline]487			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {488				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};489				let value = value as f64;490				if value < MIN_SAFE_INTEGER {491					return Err(ConvertNumValueError::Underflow)492				} else if value > MAX_SAFE_INTEGER {493					return Err(ConvertNumValueError::Overflow)494				}495				// Number is finite.496				Ok(Self(value))497			}498		}499	)+};500}501impl_try_num!(usize, isize, i64, u64);502503impl TryFrom<f64> for NumValue {504	type Error = ConvertNumValueError;505506	#[inline]507	fn try_from(value: f64) -> Result<Self, Self::Error> {508		Self::new(value).ok_or(ConvertNumValueError::NonFinite)509	}510}511impl TryFrom<f32> for NumValue {512	type Error = ConvertNumValueError;513514	#[inline]515	fn try_from(value: f32) -> Result<Self, Self::Error> {516		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)517	}518}519520/// Represents any valid Jsonnet value.521#[derive(Debug, Clone, Trace, Default)]522pub enum Val {523	/// Represents a Jsonnet boolean.524	Bool(bool),525	/// Represents a Jsonnet null value.526	#[default]527	Null,528	/// Represents a Jsonnet string.529	Str(StrValue),530	/// Represents a Jsonnet number.531	/// Should be finite, and not NaN532	/// This restriction isn't enforced by enum, as enum field can't be marked as private533	Num(NumValue),534	/// Experimental bigint535	#[cfg(feature = "exp-bigint")]536	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),537	/// Represents a Jsonnet array.538	Arr(ArrValue),539	/// Represents a Jsonnet object.540	Obj(ObjValue),541	/// Represents a Jsonnet function.542	Func(FuncVal),543}544545#[cfg(target_pointer_width = "64")]546static_assertions::assert_eq_size!(Val, [u8; 24]);547548impl From<IndexableVal> for Val {549	fn from(v: IndexableVal) -> Self {550		match v {551			IndexableVal::Str(s) => Self::string(s),552			IndexableVal::Arr(a) => Self::Arr(a),553		}554	}555}556557impl Val {558	pub const fn as_bool(&self) -> Option<bool> {559		match self {560			Self::Bool(v) => Some(*v),561			_ => None,562		}563	}564	pub const fn as_null(&self) -> Option<()> {565		match self {566			Self::Null => Some(()),567			_ => None,568		}569	}570	pub fn as_str(&self) -> Option<IStr> {571		match self {572			Self::Str(s) => Some(s.clone().into_flat()),573			_ => None,574		}575	}576	pub const fn as_num(&self) -> Option<f64> {577		match self {578			Self::Num(n) => Some(n.get()),579			_ => None,580		}581	}582	pub fn as_arr(&self) -> Option<ArrValue> {583		match self {584			Self::Arr(a) => Some(a.clone()),585			_ => None,586		}587	}588	pub fn as_obj(&self) -> Option<ObjValue> {589		match self {590			Self::Obj(o) => Some(o.clone()),591			_ => None,592		}593	}594	pub fn as_func(&self) -> Option<FuncVal> {595		match self {596			Self::Func(f) => Some(f.clone()),597			_ => None,598		}599	}600601	pub const fn value_type(&self) -> ValType {602		match self {603			Self::Str(..) => ValType::Str,604			Self::Num(..) => ValType::Num,605			#[cfg(feature = "exp-bigint")]606			Self::BigInt(..) => ValType::BigInt,607			Self::Arr(..) => ValType::Arr,608			Self::Obj(..) => ValType::Obj,609			Self::Bool(_) => ValType::Bool,610			Self::Null => ValType::Null,611			Self::Func(..) => ValType::Func,612		}613	}614615	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {616		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {617			manifest.manifest(val.clone())618		}619		manifest_dyn(self, &format)620	}621622	pub fn to_string(&self) -> Result<IStr> {623		Ok(match self {624			Self::Bool(true) => "true".into(),625			Self::Bool(false) => "false".into(),626			Self::Null => "null".into(),627			Self::Str(s) => s.clone().into_flat(),628			_ => self.manifest(ToStringFormat).map(IStr::from)?,629		})630	}631632	pub fn into_indexable(self) -> Result<IndexableVal> {633		Ok(match self {634			Self::Str(s) => IndexableVal::Str(s.into_flat()),635			Self::Arr(arr) => IndexableVal::Arr(arr),636			_ => bail!(ValueIsNotIndexable(self.value_type())),637		})638	}639640	pub fn function(function: impl Into<FuncVal>) -> Self {641		Self::Func(function.into())642	}643	pub fn string(string: impl Into<StrValue>) -> Self {644		Self::Str(string.into())645	}646	pub fn num(num: impl Into<NumValue>) -> Self {647		Self::Num(num.into())648	}649	pub fn try_num<V, E>(num: V) -> Result<Self, E>650	where651		NumValue: TryFrom<V, Error = E>,652	{653		Ok(Self::Num(num.try_into()?))654	}655}656657impl From<IStr> for Val {658	fn from(value: IStr) -> Self {659		Self::string(value)660	}661}662impl From<String> for Val {663	fn from(value: String) -> Self {664		Self::string(value)665	}666}667impl From<&str> for Val {668	fn from(value: &str) -> Self {669		Self::string(value)670	}671}672impl From<ObjValue> for Val {673	fn from(value: ObjValue) -> Self {674		Self::Obj(value)675	}676}677678const fn is_function_like(val: &Val) -> bool {679	matches!(val, Val::Func(_))680}681682/// Native implementation of `std.primitiveEquals`683pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {684	Ok(match (val_a, val_b) {685		(Val::Bool(a), Val::Bool(b)) => a == b,686		(Val::Null, Val::Null) => true,687		(Val::Str(a), Val::Str(b)) => a == b,688		(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,689		#[cfg(feature = "exp-bigint")]690		(Val::BigInt(a), Val::BigInt(b)) => a == b,691		(Val::Arr(_), Val::Arr(_)) => {692			bail!("primitiveEquals operates on primitive types, got array")693		}694		(Val::Obj(_), Val::Obj(_)) => {695			bail!("primitiveEquals operates on primitive types, got object")696		}697		(a, b) if is_function_like(a) && is_function_like(b) => {698			bail!("cannot test equality of functions")699		}700		(_, _) => false,701	})702}703704/// Native implementation of `std.equals`705pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {706	if val_a.value_type() != val_b.value_type() {707		return Ok(false);708	}709	match (val_a, val_b) {710		(Val::Arr(a), Val::Arr(b)) => {711			if ArrValue::ptr_eq(a, b) {712				return Ok(true);713			}714			if a.len() != b.len() {715				return Ok(false);716			}717			for (a, b) in a.iter().zip(b.iter()) {718				if !equals(&a?, &b?)? {719					return Ok(false);720				}721			}722			Ok(true)723		}724		(Val::Obj(a), Val::Obj(b)) => {725			if ObjValue::ptr_eq(a, b) {726				return Ok(true);727			}728			let fields = a.fields(729				#[cfg(feature = "exp-preserve-order")]730				false,731			);732			if fields733				!= b.fields(734					#[cfg(feature = "exp-preserve-order")]735					false,736				) {737				return Ok(false);738			}739			for field in fields {740				if !equals(741					&a.get(field.clone())?.expect("field exists"),742					&b.get(field)?.expect("field exists"),743				)? {744					return Ok(false);745				}746			}747			Ok(true)748		}749		(a, b) => Ok(primitive_equals(a, b)?),750	}751}
modifiedcrates/jrsonnet-macros/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-macros/Cargo.toml
+++ b/crates/jrsonnet-macros/Cargo.toml
@@ -17,3 +17,4 @@
 proc-macro2.workspace = true
 quote.workspace = true
 syn = { workspace = true, features = ["full"] }
+syn-dissect-closure.workspace = true
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -1,7 +1,7 @@
 use std::string::String;
 
 use proc_macro2::TokenStream;
-use quote::quote;
+use quote::{quote, quote_spanned};
 use syn::{
 	parenthesized,
 	parse::{Parse, ParseStream},
@@ -9,8 +9,8 @@
 	punctuated::Punctuated,
 	spanned::Spanned,
 	token::{self, Comma},
-	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
-	PathArguments, Result, ReturnType, Token, Type,
+	Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,
+	LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,
 };
 
 fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>
@@ -815,3 +815,30 @@
 	let input = parse_macro_input!(input as FormatInput);
 	input.expand().into()
 }
+
+/// Create Thunk using closure syntax
+#[proc_macro]
+#[allow(non_snake_case)]
+pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+	let input = parse_macro_input!(input as ExprClosure);
+
+	let span = input.inputs.span();
+	let move_check = input.capture.is_none().then(|| {
+		quote_spanned! {span => {
+			compile_error!("Thunk! needs to be called with move closure");
+		}}
+	});
+
+	let (env, closure, args) = syn_dissect_closure::split_env(input);
+
+	let trace_check = args.iter().map(|el| {
+		let span = el.span();
+		quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}
+	});
+
+	quote! {{
+		#move_check
+		#(#trace_check)*
+		::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))
+	}}.into()
+}