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
before · crates/jrsonnet-evaluator/src/obj.rs
1use std::{2	any::Any,3	cell::RefCell,4	fmt::Debug,5	hash::{Hash, Hasher},6	ptr::addr_of,7};89use jrsonnet_gcmodule::{Cc, Trace, Weak};10use jrsonnet_interner::IStr;11use jrsonnet_parser::{Span, Visibility};12use rustc_hash::FxHashMap;1314use crate::{15	arr::{PickObjectKeyValues, PickObjectValues},16	bail,17	error::{suggest_object_fields, Error, ErrorKind::*},18	function::{CallLocation, FuncVal},19	gc::{GcHashMap, GcHashSet, TraceBox},20	in_frame,21	operator::evaluate_add_op,22	tb,23	val::{ArrValue, ThunkValue},24	MaybeUnbound, Result, Thunk, Unbound, Val,25};2627#[cfg(not(feature = "exp-preserve-order"))]28mod ordering {29	#![allow(30		// This module works as stub for preserve-order feature31		clippy::unused_self,32	)]3334	use jrsonnet_gcmodule::Trace;3536	#[derive(Clone, Copy, Default, Debug, Trace)]37	pub struct FieldIndex(());38	impl FieldIndex {39		pub const fn next(self) -> Self {40			Self(())41		}42	}4344	#[derive(Clone, Copy, Default, Debug, Trace)]45	pub struct SuperDepth(());46	impl SuperDepth {47		pub const fn deeper(self) -> Self {48			Self(())49		}50	}5152	#[derive(Clone, Copy)]53	pub struct FieldSortKey(());54	impl FieldSortKey {55		pub const fn new(_: SuperDepth, _: FieldIndex) -> Self {56			Self(())57		}58	}59}6061#[cfg(feature = "exp-preserve-order")]62mod ordering {63	use std::cmp::Reverse;6465	use jrsonnet_gcmodule::Trace;6667	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]68	pub struct FieldIndex(u32);69	impl FieldIndex {70		pub fn next(self) -> Self {71			Self(self.0 + 1)72		}73	}7475	#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]76	pub struct SuperDepth(u32);77	impl SuperDepth {78		pub fn deeper(self) -> Self {79			Self(self.0 + 1)80		}81	}8283	#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]84	pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);85	impl FieldSortKey {86		pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {87			Self(Reverse(depth), index)88		}89	}90}9192use ordering::{FieldIndex, FieldSortKey, SuperDepth};9394// 0 - add95//  12 - visibility96#[derive(Clone, Copy)]97pub struct ObjFieldFlags(u8);98impl ObjFieldFlags {99	fn new(add: bool, visibility: Visibility) -> Self {100		let mut v = 0;101		if add {102			v |= 1;103		}104		v |= match visibility {105			Visibility::Normal => 0b000,106			Visibility::Hidden => 0b010,107			Visibility::Unhide => 0b100,108		};109		Self(v)110	}111	pub fn add(&self) -> bool {112		self.0 & 1 != 0113	}114	pub fn visibility(&self) -> Visibility {115		match (self.0 & 0b110) >> 1 {116			0b00 => Visibility::Normal,117			0b01 => Visibility::Hidden,118			0b10 => Visibility::Unhide,119			_ => unreachable!(),120		}121	}122}123impl Debug for ObjFieldFlags {124	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {125		f.debug_struct("ObjFieldFlags")126			.field("add", &self.add())127			.field("visibility", &self.visibility())128			.finish()129	}130}131132#[allow(clippy::module_name_repetitions)]133#[derive(Debug, Trace)]134pub struct ObjMember {135	#[trace(skip)]136	flags: ObjFieldFlags,137	original_index: FieldIndex,138	pub invoke: MaybeUnbound,139	pub location: Option<Span>,140}141142pub trait ObjectAssertion: Trace {143	fn run(&self, super_obj: Option<ObjValue>, this: Option<ObjValue>) -> Result<()>;144}145146// Field => This147148#[derive(Trace)]149enum CacheValue {150	Cached(Val),151	NotFound,152	Pending,153	Errored(Error),154}155156#[allow(clippy::module_name_repetitions)]157#[derive(Trace)]158#[trace(tracking(force))]159pub struct OopObject {160	sup: Option<ObjValue>,161	// this: Option<ObjValue>,162	assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,163	assertions_ran: RefCell<GcHashSet<ObjValue>>,164	this_entries: Cc<GcHashMap<IStr, ObjMember>>,165	value_cache: RefCell<GcHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,166}167impl Debug for OopObject {168	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {169		f.debug_struct("OopObject")170			.field("sup", &self.sup)171			// .field("assertions", &self.assertions)172			// .field("assertions_ran", &self.assertions_ran)173			.field("this_entries", &self.this_entries)174			// .field("value_cache", &self.value_cache)175			.finish_non_exhaustive()176	}177}178179type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a;180181pub trait ObjectLike: Trace + Any + Debug {182	fn extend_from(&self, sup: ObjValue) -> ObjValue;183	/// When using standalone super in object, `this.super_obj.with_this(this)` is executed184	fn with_this(&self, me: ObjValue, this: ObjValue) -> ObjValue {185		ObjValue::new(ThisOverride { inner: me, this })186	}187	fn this(&self) -> Option<ObjValue> {188		None189	}190	fn len(&self) -> usize;191	fn is_empty(&self) -> bool;192	// If callback returns false, iteration stops193	fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool;194195	fn has_field_include_hidden(&self, name: IStr) -> bool;196	fn has_field(&self, name: IStr) -> bool;197198	fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>>;199	fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<Val>>;200	fn field_visibility(&self, field: IStr) -> Option<Visibility>;201202	fn run_assertions_raw(&self, this: ObjValue) -> Result<()>;203}204205#[derive(Clone, Trace)]206pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak<TraceBox<dyn ObjectLike>>);207208impl PartialEq for WeakObjValue {209	fn eq(&self, other: &Self) -> bool {210		Weak::ptr_eq(&self.0, &other.0)211	}212}213214impl Eq for WeakObjValue {}215impl Hash for WeakObjValue {216	fn hash<H: Hasher>(&self, hasher: &mut H) {217		// Safety: usize is POD218		let addr = unsafe { *std::ptr::addr_of!(self.0).cast() };219		hasher.write_usize(addr);220	}221}222223#[allow(clippy::module_name_repetitions)]224#[derive(Clone, Trace, Debug)]225pub struct ObjValue(pub(crate) Cc<TraceBox<dyn ObjectLike>>);226227#[derive(Debug, Trace)]228struct EmptyObject;229impl ObjectLike for EmptyObject {230	fn extend_from(&self, sup: ObjValue) -> ObjValue {231		// obj + {} == obj232		sup233	}234235	fn this(&self) -> Option<ObjValue> {236		None237	}238239	fn len(&self) -> usize {240		0241	}242243	fn is_empty(&self) -> bool {244		true245	}246247	fn enum_fields(&self, _depth: SuperDepth, _handler: &mut EnumFieldsHandler<'_>) -> bool {248		false249	}250251	fn has_field_include_hidden(&self, _name: IStr) -> bool {252		false253	}254255	fn has_field(&self, _name: IStr) -> bool {256		false257	}258259	fn get_for(&self, _key: IStr, _this: ObjValue) -> Result<Option<Val>> {260		Ok(None)261	}262	fn get_for_uncached(&self, _key: IStr, _this: ObjValue) -> Result<Option<Val>> {263		Ok(None)264	}265266	fn run_assertions_raw(&self, _this: ObjValue) -> Result<()> {267		Ok(())268	}269270	fn field_visibility(&self, _field: IStr) -> Option<Visibility> {271		None272	}273}274275#[derive(Trace, Debug)]276struct ThisOverride {277	inner: ObjValue,278	this: ObjValue,279}280impl ObjectLike for ThisOverride {281	fn with_this(&self, _me: ObjValue, this: ObjValue) -> ObjValue {282		ObjValue::new(Self {283			inner: self.inner.clone(),284			this,285		})286	}287288	fn extend_from(&self, sup: ObjValue) -> ObjValue {289		self.inner.extend_from(sup).with_this(self.this.clone())290	}291292	fn this(&self) -> Option<ObjValue> {293		Some(self.this.clone())294	}295296	fn len(&self) -> usize {297		self.inner.len()298	}299300	fn is_empty(&self) -> bool {301		self.inner.is_empty()302	}303304	fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {305		self.inner.enum_fields(depth, handler)306	}307308	fn has_field_include_hidden(&self, name: IStr) -> bool {309		self.inner.has_field_include_hidden(name)310	}311312	fn has_field(&self, name: IStr) -> bool {313		self.inner.has_field(name)314	}315316	fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {317		self.inner.get_for(key, this)318	}319320	fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {321		self.inner.get_raw(key, this)322	}323324	fn field_visibility(&self, field: IStr) -> Option<Visibility> {325		self.inner.field_visibility(field)326	}327328	fn run_assertions_raw(&self, this: ObjValue) -> Result<()> {329		self.inner.run_assertions_raw(this)330	}331}332333impl ObjValue {334	pub fn new(v: impl ObjectLike) -> Self {335		Self(Cc::new(tb!(v)))336	}337	pub fn new_empty() -> Self {338		Self::new(EmptyObject)339	}340	pub fn builder() -> ObjValueBuilder {341		ObjValueBuilder::new()342	}343	pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {344		ObjValueBuilder::with_capacity(capacity)345	}346	pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {347		let mut out = ObjValueBuilder::with_capacity(1);348		out.with_super(self);349		let mut member = out.field(key);350		if value.flags.add() {351			member = member.add();352		}353		if let Some(loc) = value.location {354			member = member.with_location(loc);355		}356		let _ = member357			.with_visibility(value.flags.visibility())358			.binding(value.invoke);359		out.build()360	}361	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {362		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())363	}364365	#[must_use]366	pub fn extend_from(&self, sup: Self) -> Self {367		self.0.extend_from(sup)368	}369	#[must_use]370	pub fn with_this(&self, this: Self) -> Self {371		self.0.with_this(self.clone(), this)372	}373	pub fn len(&self) -> usize {374		self.0.len()375	}376	pub fn is_empty(&self) -> bool {377		self.0.is_empty()378	}379	pub fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {380		self.0.enum_fields(depth, handler)381	}382383	pub fn has_field_include_hidden(&self, name: IStr) -> bool {384		self.0.has_field_include_hidden(name)385	}386	pub fn has_field(&self, name: IStr) -> bool {387		self.0.has_field(name)388	}389	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {390		if include_hidden {391			self.has_field_include_hidden(name)392		} else {393			self.has_field(name)394		}395	}396397	pub fn get(&self, key: IStr) -> Result<Option<Val>> {398		self.run_assertions()?;399		self.get_for(key, self.0.this().unwrap_or_else(|| self.clone()))400	}401402	pub fn get_for(&self, key: IStr, this: Self) -> Result<Option<Val>> {403		self.0.get_for(key, this)404	}405406	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {407		let Some(value) = self.get(key.clone())? else {408			let suggestions = suggest_object_fields(self, key.clone());409			bail!(NoSuchField(key, suggestions))410		};411		Ok(value)412	}413414	fn get_raw(&self, key: IStr, this: Self) -> Result<Option<Val>> {415		self.0.get_for_uncached(key, this)416	}417418	fn field_visibility(&self, field: IStr) -> Option<Visibility> {419		self.0.field_visibility(field)420	}421422	pub fn run_assertions(&self) -> Result<()> {423		// FIXME: Should it use `self.0.this()` in case of standalone super?424		self.run_assertions_raw(self.clone())425	}426	fn run_assertions_raw(&self, this: Self) -> Result<()> {427		self.0.run_assertions_raw(this)428	}429430	pub fn iter(431		&self,432		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,433	) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {434		let fields = self.fields(435			#[cfg(feature = "exp-preserve-order")]436			preserve_order,437		);438		fields.into_iter().map(|field| {439			(440				field.clone(),441				self.get(field)442					.map(|opt| opt.expect("iterating over keys, field exists")),443			)444		})445	}446	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {447		#[derive(Trace)]448		struct ThunkGet {449			obj: ObjValue,450			key: IStr,451		}452		impl ThunkValue for ThunkGet {453			type Output = Val;454455			fn get(self: Box<Self>) -> Result<Self::Output> {456				Ok(self.obj.get(self.key)?.expect("field exists"))457			}458		}459460		if !self.has_field_ex(key.clone(), true) {461			return None;462		}463		Some(Thunk::new(ThunkGet {464			obj: self.clone(),465			key,466		}))467	}468	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {469		#[derive(Trace)]470		struct ThunkGet {471			obj: ObjValue,472			key: IStr,473		}474		impl ThunkValue for ThunkGet {475			type Output = Val;476477			fn get(self: Box<Self>) -> Result<Self::Output> {478				self.obj.get_or_bail(self.key)479			}480		}481482		Thunk::new(ThunkGet {483			obj: self.clone(),484			key,485		})486	}487	pub fn ptr_eq(a: &Self, b: &Self) -> bool {488		Cc::ptr_eq(&a.0, &b.0)489	}490	pub fn downgrade(self) -> WeakObjValue {491		WeakObjValue(self.0.downgrade())492	}493	fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {494		let mut out = FxHashMap::default();495		self.enum_fields(496			SuperDepth::default(),497			&mut |depth, index, name, visibility| {498				let new_sort_key = FieldSortKey::new(depth, index);499				let entry = out.entry(name);500				let (visible, _) = entry.or_insert((true, new_sort_key));501				match visibility {502					Visibility::Normal => {}503					Visibility::Hidden => {504						*visible = false;505					}506					Visibility::Unhide => {507						*visible = true;508					}509				};510				false511			},512		);513		out514	}515	pub fn fields_ex(516		&self,517		include_hidden: bool,518		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,519	) -> Vec<IStr> {520		#[cfg(feature = "exp-preserve-order")]521		if preserve_order {522			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self523				.fields_visibility()524				.into_iter()525				.filter(|(_, (visible, _))| include_hidden || *visible)526				.enumerate()527				.map(|(idx, (k, (_, sk)))| (k, (sk, idx)))528				.unzip();529			keys.sort_unstable_by_key(|v| v.0);530			// Reorder in-place by resulting indexes531			for i in 0..fields.len() {532				let x = fields[i].clone();533				let mut j = i;534				loop {535					let k = keys[j].1;536					keys[j].1 = j;537					if k == i {538						break;539					}540					fields[j] = fields[k].clone();541					j = k;542				}543				fields[j] = x;544			}545			return fields;546		}547548		let mut fields: Vec<_> = self549			.fields_visibility()550			.into_iter()551			.filter(|(_, (visible, _))| include_hidden || *visible)552			.map(|(k, _)| k)553			.collect();554		fields.sort_unstable();555		fields556	}557	pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec<IStr> {558		self.fields_ex(559			false,560			#[cfg(feature = "exp-preserve-order")]561			preserve_order,562		)563	}564	pub fn values_ex(565		&self,566		include_hidden: bool,567		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,568	) -> ArrValue {569		ArrValue::new(PickObjectValues::new(570			self.clone(),571			self.fields_ex(572				include_hidden,573				#[cfg(feature = "exp-preserve-order")]574				preserve_order,575			),576		))577	}578	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {579		self.values_ex(580			false,581			#[cfg(feature = "exp-preserve-order")]582			preserve_order,583		)584	}585	pub fn key_values_ex(586		&self,587		include_hidden: bool,588		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,589	) -> ArrValue {590		ArrValue::new(PickObjectKeyValues::new(591			self.clone(),592			self.fields_ex(593				include_hidden,594				#[cfg(feature = "exp-preserve-order")]595				preserve_order,596			),597		))598	}599	pub fn key_values(600		&self,601		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,602	) -> ArrValue {603		self.key_values_ex(604			false,605			#[cfg(feature = "exp-preserve-order")]606			preserve_order,607		)608	}609}610611impl OopObject {612	pub fn new(613		sup: Option<ObjValue>,614		this_entries: Cc<GcHashMap<IStr, ObjMember>>,615		assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,616	) -> Self {617		Self {618			sup,619			// this: None,620			assertions,621			assertions_ran: RefCell::new(GcHashSet::new()),622			this_entries,623			value_cache: RefCell::new(GcHashMap::new()),624		}625	}626627	fn evaluate_this(&self, v: &ObjMember, real_this: ObjValue) -> Result<Val> {628		v.invoke.evaluate(self.sup.clone(), Some(real_this))629	}630631	// FIXME: Duplication between ObjValue and OopObject632	fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {633		let mut out = FxHashMap::default();634		self.enum_fields(635			SuperDepth::default(),636			&mut |depth, index, name, visibility| {637				let new_sort_key = FieldSortKey::new(depth, index);638				let entry = out.entry(name);639				let (visible, _) = entry.or_insert((true, new_sort_key));640				match visibility {641					Visibility::Normal => {}642					Visibility::Hidden => {643						*visible = false;644					}645					Visibility::Unhide => {646						*visible = true;647					}648				};649				false650			},651		);652		out653	}654}655656impl ObjectLike for OopObject {657	fn extend_from(&self, sup: ObjValue) -> ObjValue {658		ObjValue::new(match &self.sup {659			None => Self::new(660				Some(sup),661				self.this_entries.clone(),662				self.assertions.clone(),663			),664			Some(v) => Self::new(665				Some(v.extend_from(sup)),666				self.this_entries.clone(),667				self.assertions.clone(),668			),669		})670	}671672	fn len(&self) -> usize {673		// Maybe it will be better to not compute sort key here?674		self.fields_visibility()675			.into_iter()676			.filter(|(_, (visible, _))| *visible)677			.count()678	}679680	/// Returns false only if there is any visible entry.681	///682	/// Note that object with hidden fields `{a:: 1}` will be reported as empty here.683	fn is_empty(&self) -> bool {684		self.len() == 0685	}686687	/// Run callback for every field found in object688	///689	/// Returns true if ended prematurely690	fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {691		if let Some(s) = &self.sup {692			if s.enum_fields(depth.deeper(), handler) {693				return true;694			}695		}696		for (name, member) in self.this_entries.iter() {697			if handler(698				depth,699				member.original_index,700				name.clone(),701				member.flags.visibility(),702			) {703				return true;704			}705		}706		false707	}708709	fn has_field_include_hidden(&self, name: IStr) -> bool {710		if self.this_entries.contains_key(&name) {711			true712		} else if let Some(super_obj) = &self.sup {713			super_obj.has_field_include_hidden(name)714		} else {715			false716		}717	}718	fn has_field(&self, name: IStr) -> bool {719		self.field_visibility(name)720			.map_or(false, |v| v.is_visible())721	}722723	fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {724		let cache_key = (key.clone(), Some(this.clone().downgrade()));725		if let Some(v) = self.value_cache.borrow().get(&cache_key) {726			return Ok(match v {727				CacheValue::Cached(v) => Some(v.clone()),728				CacheValue::NotFound => None,729				CacheValue::Pending => bail!(InfiniteRecursionDetected),730				CacheValue::Errored(e) => return Err(e.clone()),731			});732		}733		self.value_cache734			.borrow_mut()735			.insert(cache_key.clone(), CacheValue::Pending);736		let value = self.get_for_uncached(key, this).map_err(|e| {737			self.value_cache738				.borrow_mut()739				.insert(cache_key.clone(), CacheValue::Errored(e.clone()));740			e741		})?;742		self.value_cache.borrow_mut().insert(743			cache_key,744			value745				.as_ref()746				.map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())),747		);748		Ok(value)749	}750	fn get_for_uncached(&self, key: IStr, real_this: ObjValue) -> Result<Option<Val>> {751		match (self.this_entries.get(&key), &self.sup) {752			(Some(k), None) => Ok(Some(self.evaluate_this(k, real_this)?)),753			(Some(k), Some(super_obj)) => {754				let our = self.evaluate_this(k, real_this.clone())?;755				if k.flags.add() {756					super_obj757						.get_raw(key, real_this)?758						.map_or(Ok(Some(our.clone())), |v| {759							Ok(Some(evaluate_add_op(&v, &our)?))760						})761				} else {762					Ok(Some(our))763				}764			}765			(None, Some(super_obj)) => super_obj.get_raw(key, real_this),766			(None, None) => Ok(None),767		}768	}769	fn field_visibility(&self, name: IStr) -> Option<Visibility> {770		if let Some(m) = self.this_entries.get(&name) {771			Some(match &m.flags.visibility() {772				Visibility::Normal => self773					.sup774					.as_ref()775					.and_then(|super_obj| super_obj.field_visibility(name))776					.unwrap_or(Visibility::Normal),777				v => *v,778			})779		} else if let Some(super_obj) = &self.sup {780			super_obj.field_visibility(name)781		} else {782			None783		}784	}785786	fn run_assertions_raw(&self, real_this: ObjValue) -> Result<()> {787		if self.assertions.is_empty() {788			if let Some(super_obj) = &self.sup {789				super_obj.run_assertions_raw(real_this)?;790			}791			return Ok(());792		}793		if self.assertions_ran.borrow_mut().insert(real_this.clone()) {794			for assertion in self.assertions.iter() {795				if let Err(e) = assertion.run(self.sup.clone(), Some(real_this.clone())) {796					self.assertions_ran.borrow_mut().remove(&real_this);797					return Err(e);798				}799			}800			if let Some(super_obj) = &self.sup {801				super_obj.run_assertions_raw(real_this)?;802			}803		}804		Ok(())805	}806}807808impl PartialEq for ObjValue {809	fn eq(&self, other: &Self) -> bool {810		Cc::ptr_eq(&self.0, &other.0)811	}812}813814impl Eq for ObjValue {}815impl Hash for ObjValue {816	fn hash<H: Hasher>(&self, hasher: &mut H) {817		hasher.write_usize(addr_of!(*self.0) as usize);818	}819}820821#[allow(clippy::module_name_repetitions)]822pub struct ObjValueBuilder {823	sup: Option<ObjValue>,824	map: GcHashMap<IStr, ObjMember>,825	assertions: Vec<TraceBox<dyn ObjectAssertion>>,826	next_field_index: FieldIndex,827}828impl ObjValueBuilder {829	pub fn new() -> Self {830		Self::with_capacity(0)831	}832	pub fn with_capacity(capacity: usize) -> Self {833		Self {834			sup: None,835			map: GcHashMap::with_capacity(capacity),836			assertions: Vec::new(),837			next_field_index: FieldIndex::default(),838		}839	}840	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {841		self.assertions.reserve_exact(capacity);842		self843	}844	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {845		self.sup = Some(super_obj);846		self847	}848849	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {850		self.assertions.push(tb!(assertion));851		self852	}853	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {854		let field_index = self.next_field_index;855		self.next_field_index = self.next_field_index.next();856		ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)857	}858	/// Preset for common method definiton pattern:859	/// Create a hidden field with the function value.860	///861	/// `.field(name).hide().value(Val::function(value))`862	pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {863		self.field(name).hide().value(Val::Func(value.into()));864		self865	}866	pub fn try_method(867		&mut self,868		name: impl Into<IStr>,869		value: impl Into<FuncVal>,870	) -> Result<&mut Self> {871		self.field(name).hide().try_value(Val::Func(value.into()))?;872		Ok(self)873	}874875	pub fn build(self) -> ObjValue {876		if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() {877			return ObjValue::new_empty();878		}879		ObjValue::new(OopObject::new(880			self.sup,881			Cc::new(self.map),882			Cc::new(self.assertions),883		))884	}885}886impl Default for ObjValueBuilder {887	fn default() -> Self {888		Self::with_capacity(0)889	}890}891892#[allow(clippy::module_name_repetitions)]893#[must_use = "value not added unless binding() was called"]894pub struct ObjMemberBuilder<Kind> {895	kind: Kind,896	name: IStr,897	add: bool,898	visibility: Visibility,899	original_index: FieldIndex,900	location: Option<Span>,901}902903#[allow(clippy::missing_const_for_fn)]904impl<Kind> ObjMemberBuilder<Kind> {905	pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self {906		Self {907			kind,908			name,909			original_index,910			add: false,911			visibility: Visibility::Normal,912			location: None,913		}914	}915916	pub const fn with_add(mut self, add: bool) -> Self {917		self.add = add;918		self919	}920	pub fn add(self) -> Self {921		self.with_add(true)922	}923	pub fn with_visibility(mut self, visibility: Visibility) -> Self {924		self.visibility = visibility;925		self926	}927	pub fn hide(self) -> Self {928		self.with_visibility(Visibility::Hidden)929	}930	pub fn with_location(mut self, location: Span) -> Self {931		self.location = Some(location);932		self933	}934	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {935		(936			self.kind,937			self.name,938			ObjMember {939				flags: ObjFieldFlags::new(self.add, self.visibility),940				original_index: self.original_index,941				invoke: binding,942				location: self.location,943			},944		)945	}946}947948pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);949impl ObjMemberBuilder<ValueBuilder<'_>> {950	/// Inserts value, replacing if it is already defined951	pub fn value(self, value: impl Into<Val>) {952		let (receiver, name, member) =953			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));954		let entry = receiver.0.map.entry(name);955		entry.insert(member);956	}957958	/// Tries to insert value, returns an error if it was already defined959	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {960		self.try_thunk(Thunk::evaluated(value.into()))961	}962	pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {963		self.binding(MaybeUnbound::Bound(value.into()))964	}965	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {966		self.binding(MaybeUnbound::Unbound(Cc::new(tb!(bindable))))967	}968	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {969		let (receiver, name, member) = self.build_member(binding);970		let location = member.location.clone();971		let old = receiver.0.map.insert(name.clone(), member);972		if old.is_some() {973			in_frame(974				CallLocation(location.as_ref()),975				|| format!("field <{}> initializtion", name.clone()),976				|| bail!(DuplicateFieldName(name.clone())),977			)?;978		}979		Ok(())980	}981}982983pub struct ExtendBuilder<'v>(&'v mut ObjValue);984impl ObjMemberBuilder<ExtendBuilder<'_>> {985	pub fn value(self, value: impl Into<Val>) {986		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));987	}988	pub fn bindable(self, bindable: TraceBox<dyn Unbound<Bound = Val>>) {989		self.binding(MaybeUnbound::Unbound(Cc::new(bindable)));990	}991	pub fn binding(self, binding: MaybeUnbound) {992		let (receiver, name, member) = self.build_member(binding);993		let new = receiver.0.clone();994		*receiver.0 = new.extend_with_raw_member(name, member);995	}996}
after · crates/jrsonnet-evaluator/src/obj.rs
1use std::{2	any::Any,3	cell::RefCell,4	fmt::Debug,5	hash::{Hash, Hasher},6	ptr::addr_of,7};89use jrsonnet_gcmodule::{Cc, Trace, Weak};10use jrsonnet_interner::IStr;11use jrsonnet_parser::{Span, Visibility};12use rustc_hash::FxHashMap;1314use crate::{15	arr::{PickObjectKeyValues, PickObjectValues},16	bail,17	error::{suggest_object_fields, Error, ErrorKind::*},18	function::{CallLocation, FuncVal},19	gc::{GcHashMap, GcHashSet, TraceBox},20	in_frame,21	operator::evaluate_add_op,22	tb,23	val::ArrValue,24	MaybeUnbound, Result, Thunk, Unbound, Val,25};2627#[cfg(not(feature = "exp-preserve-order"))]28mod ordering {29	#![allow(30		// This module works as stub for preserve-order feature31		clippy::unused_self,32	)]3334	use jrsonnet_gcmodule::Trace;3536	#[derive(Clone, Copy, Default, Debug, Trace)]37	pub struct FieldIndex(());38	impl FieldIndex {39		pub const fn next(self) -> Self {40			Self(())41		}42	}4344	#[derive(Clone, Copy, Default, Debug, Trace)]45	pub struct SuperDepth(());46	impl SuperDepth {47		pub const fn deeper(self) -> Self {48			Self(())49		}50	}5152	#[derive(Clone, Copy)]53	pub struct FieldSortKey(());54	impl FieldSortKey {55		pub const fn new(_: SuperDepth, _: FieldIndex) -> Self {56			Self(())57		}58	}59}6061#[cfg(feature = "exp-preserve-order")]62mod ordering {63	use std::cmp::Reverse;6465	use jrsonnet_gcmodule::Trace;6667	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]68	pub struct FieldIndex(u32);69	impl FieldIndex {70		pub fn next(self) -> Self {71			Self(self.0 + 1)72		}73	}7475	#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]76	pub struct SuperDepth(u32);77	impl SuperDepth {78		pub fn deeper(self) -> Self {79			Self(self.0 + 1)80		}81	}8283	#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]84	pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);85	impl FieldSortKey {86		pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {87			Self(Reverse(depth), index)88		}89	}90}9192use ordering::{FieldIndex, FieldSortKey, SuperDepth};9394// 0 - add95//  12 - visibility96#[derive(Clone, Copy)]97pub struct ObjFieldFlags(u8);98impl ObjFieldFlags {99	fn new(add: bool, visibility: Visibility) -> Self {100		let mut v = 0;101		if add {102			v |= 1;103		}104		v |= match visibility {105			Visibility::Normal => 0b000,106			Visibility::Hidden => 0b010,107			Visibility::Unhide => 0b100,108		};109		Self(v)110	}111	pub fn add(&self) -> bool {112		self.0 & 1 != 0113	}114	pub fn visibility(&self) -> Visibility {115		match (self.0 & 0b110) >> 1 {116			0b00 => Visibility::Normal,117			0b01 => Visibility::Hidden,118			0b10 => Visibility::Unhide,119			_ => unreachable!(),120		}121	}122}123impl Debug for ObjFieldFlags {124	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {125		f.debug_struct("ObjFieldFlags")126			.field("add", &self.add())127			.field("visibility", &self.visibility())128			.finish()129	}130}131132#[allow(clippy::module_name_repetitions)]133#[derive(Debug, Trace)]134pub struct ObjMember {135	#[trace(skip)]136	flags: ObjFieldFlags,137	original_index: FieldIndex,138	pub invoke: MaybeUnbound,139	pub location: Option<Span>,140}141142pub trait ObjectAssertion: Trace {143	fn run(&self, super_obj: Option<ObjValue>, this: Option<ObjValue>) -> Result<()>;144}145146// Field => This147148#[derive(Trace)]149enum CacheValue {150	Cached(Val),151	NotFound,152	Pending,153	Errored(Error),154}155156#[allow(clippy::module_name_repetitions)]157#[derive(Trace)]158#[trace(tracking(force))]159pub struct OopObject {160	sup: Option<ObjValue>,161	// this: Option<ObjValue>,162	assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,163	assertions_ran: RefCell<GcHashSet<ObjValue>>,164	this_entries: Cc<GcHashMap<IStr, ObjMember>>,165	value_cache: RefCell<GcHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,166}167impl Debug for OopObject {168	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {169		f.debug_struct("OopObject")170			.field("sup", &self.sup)171			// .field("assertions", &self.assertions)172			// .field("assertions_ran", &self.assertions_ran)173			.field("this_entries", &self.this_entries)174			// .field("value_cache", &self.value_cache)175			.finish_non_exhaustive()176	}177}178179type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a;180181pub trait ObjectLike: Trace + Any + Debug {182	fn extend_from(&self, sup: ObjValue) -> ObjValue;183	/// When using standalone super in object, `this.super_obj.with_this(this)` is executed184	fn with_this(&self, me: ObjValue, this: ObjValue) -> ObjValue {185		ObjValue::new(ThisOverride { inner: me, this })186	}187	fn this(&self) -> Option<ObjValue> {188		None189	}190	fn len(&self) -> usize;191	fn is_empty(&self) -> bool;192	// If callback returns false, iteration stops193	fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool;194195	fn has_field_include_hidden(&self, name: IStr) -> bool;196	fn has_field(&self, name: IStr) -> bool;197198	fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>>;199	fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<Val>>;200	fn field_visibility(&self, field: IStr) -> Option<Visibility>;201202	fn run_assertions_raw(&self, this: ObjValue) -> Result<()>;203}204205#[derive(Clone, Trace)]206pub struct WeakObjValue(#[trace(skip)] pub(crate) Weak<TraceBox<dyn ObjectLike>>);207208impl PartialEq for WeakObjValue {209	fn eq(&self, other: &Self) -> bool {210		Weak::ptr_eq(&self.0, &other.0)211	}212}213214impl Eq for WeakObjValue {}215impl Hash for WeakObjValue {216	fn hash<H: Hasher>(&self, hasher: &mut H) {217		// Safety: usize is POD218		let addr = unsafe { *std::ptr::addr_of!(self.0).cast() };219		hasher.write_usize(addr);220	}221}222223#[allow(clippy::module_name_repetitions)]224#[derive(Clone, Trace, Debug)]225pub struct ObjValue(pub(crate) Cc<TraceBox<dyn ObjectLike>>);226227#[derive(Debug, Trace)]228struct EmptyObject;229impl ObjectLike for EmptyObject {230	fn extend_from(&self, sup: ObjValue) -> ObjValue {231		// obj + {} == obj232		sup233	}234235	fn this(&self) -> Option<ObjValue> {236		None237	}238239	fn len(&self) -> usize {240		0241	}242243	fn is_empty(&self) -> bool {244		true245	}246247	fn enum_fields(&self, _depth: SuperDepth, _handler: &mut EnumFieldsHandler<'_>) -> bool {248		false249	}250251	fn has_field_include_hidden(&self, _name: IStr) -> bool {252		false253	}254255	fn has_field(&self, _name: IStr) -> bool {256		false257	}258259	fn get_for(&self, _key: IStr, _this: ObjValue) -> Result<Option<Val>> {260		Ok(None)261	}262	fn get_for_uncached(&self, _key: IStr, _this: ObjValue) -> Result<Option<Val>> {263		Ok(None)264	}265266	fn run_assertions_raw(&self, _this: ObjValue) -> Result<()> {267		Ok(())268	}269270	fn field_visibility(&self, _field: IStr) -> Option<Visibility> {271		None272	}273}274275#[derive(Trace, Debug)]276struct ThisOverride {277	inner: ObjValue,278	this: ObjValue,279}280impl ObjectLike for ThisOverride {281	fn with_this(&self, _me: ObjValue, this: ObjValue) -> ObjValue {282		ObjValue::new(Self {283			inner: self.inner.clone(),284			this,285		})286	}287288	fn extend_from(&self, sup: ObjValue) -> ObjValue {289		self.inner.extend_from(sup).with_this(self.this.clone())290	}291292	fn this(&self) -> Option<ObjValue> {293		Some(self.this.clone())294	}295296	fn len(&self) -> usize {297		self.inner.len()298	}299300	fn is_empty(&self) -> bool {301		self.inner.is_empty()302	}303304	fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {305		self.inner.enum_fields(depth, handler)306	}307308	fn has_field_include_hidden(&self, name: IStr) -> bool {309		self.inner.has_field_include_hidden(name)310	}311312	fn has_field(&self, name: IStr) -> bool {313		self.inner.has_field(name)314	}315316	fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {317		self.inner.get_for(key, this)318	}319320	fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {321		self.inner.get_raw(key, this)322	}323324	fn field_visibility(&self, field: IStr) -> Option<Visibility> {325		self.inner.field_visibility(field)326	}327328	fn run_assertions_raw(&self, this: ObjValue) -> Result<()> {329		self.inner.run_assertions_raw(this)330	}331}332333impl ObjValue {334	pub fn new(v: impl ObjectLike) -> Self {335		Self(Cc::new(tb!(v)))336	}337	pub fn new_empty() -> Self {338		Self::new(EmptyObject)339	}340	pub fn builder() -> ObjValueBuilder {341		ObjValueBuilder::new()342	}343	pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {344		ObjValueBuilder::with_capacity(capacity)345	}346	pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {347		let mut out = ObjValueBuilder::with_capacity(1);348		out.with_super(self);349		let mut member = out.field(key);350		if value.flags.add() {351			member = member.add();352		}353		if let Some(loc) = value.location {354			member = member.with_location(loc);355		}356		let _ = member357			.with_visibility(value.flags.visibility())358			.binding(value.invoke);359		out.build()360	}361	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {362		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())363	}364365	#[must_use]366	pub fn extend_from(&self, sup: Self) -> Self {367		self.0.extend_from(sup)368	}369	#[must_use]370	pub fn with_this(&self, this: Self) -> Self {371		self.0.with_this(self.clone(), this)372	}373	pub fn len(&self) -> usize {374		self.0.len()375	}376	pub fn is_empty(&self) -> bool {377		self.0.is_empty()378	}379	pub fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {380		self.0.enum_fields(depth, handler)381	}382383	pub fn has_field_include_hidden(&self, name: IStr) -> bool {384		self.0.has_field_include_hidden(name)385	}386	pub fn has_field(&self, name: IStr) -> bool {387		self.0.has_field(name)388	}389	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {390		if include_hidden {391			self.has_field_include_hidden(name)392		} else {393			self.has_field(name)394		}395	}396397	pub fn get(&self, key: IStr) -> Result<Option<Val>> {398		self.run_assertions()?;399		self.get_for(key, self.0.this().unwrap_or_else(|| self.clone()))400	}401402	pub fn get_for(&self, key: IStr, this: Self) -> Result<Option<Val>> {403		self.0.get_for(key, this)404	}405406	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {407		let Some(value) = self.get(key.clone())? else {408			let suggestions = suggest_object_fields(self, key.clone());409			bail!(NoSuchField(key, suggestions))410		};411		Ok(value)412	}413414	fn get_raw(&self, key: IStr, this: Self) -> Result<Option<Val>> {415		self.0.get_for_uncached(key, this)416	}417418	fn field_visibility(&self, field: IStr) -> Option<Visibility> {419		self.0.field_visibility(field)420	}421422	pub fn run_assertions(&self) -> Result<()> {423		// FIXME: Should it use `self.0.this()` in case of standalone super?424		self.run_assertions_raw(self.clone())425	}426	fn run_assertions_raw(&self, this: Self) -> Result<()> {427		self.0.run_assertions_raw(this)428	}429430	pub fn iter(431		&self,432		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,433	) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {434		let fields = self.fields(435			#[cfg(feature = "exp-preserve-order")]436			preserve_order,437		);438		fields.into_iter().map(|field| {439			(440				field.clone(),441				self.get(field)442					.map(|opt| opt.expect("iterating over keys, field exists")),443			)444		})445	}446	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {447		if !self.has_field_ex(key.clone(), true) {448			return None;449		}450		let obj = self.clone();451452		Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))453	}454	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {455		let obj = self.clone();456		Thunk!(move || obj.get_or_bail(key))457	}458	pub fn ptr_eq(a: &Self, b: &Self) -> bool {459		Cc::ptr_eq(&a.0, &b.0)460	}461	pub fn downgrade(self) -> WeakObjValue {462		WeakObjValue(self.0.downgrade())463	}464	fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {465		let mut out = FxHashMap::default();466		self.enum_fields(467			SuperDepth::default(),468			&mut |depth, index, name, visibility| {469				let new_sort_key = FieldSortKey::new(depth, index);470				let entry = out.entry(name);471				let (visible, _) = entry.or_insert((true, new_sort_key));472				match visibility {473					Visibility::Normal => {}474					Visibility::Hidden => {475						*visible = false;476					}477					Visibility::Unhide => {478						*visible = true;479					}480				};481				false482			},483		);484		out485	}486	pub fn fields_ex(487		&self,488		include_hidden: bool,489		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,490	) -> Vec<IStr> {491		#[cfg(feature = "exp-preserve-order")]492		if preserve_order {493			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self494				.fields_visibility()495				.into_iter()496				.filter(|(_, (visible, _))| include_hidden || *visible)497				.enumerate()498				.map(|(idx, (k, (_, sk)))| (k, (sk, idx)))499				.unzip();500			keys.sort_unstable_by_key(|v| v.0);501			// Reorder in-place by resulting indexes502			for i in 0..fields.len() {503				let x = fields[i].clone();504				let mut j = i;505				loop {506					let k = keys[j].1;507					keys[j].1 = j;508					if k == i {509						break;510					}511					fields[j] = fields[k].clone();512					j = k;513				}514				fields[j] = x;515			}516			return fields;517		}518519		let mut fields: Vec<_> = self520			.fields_visibility()521			.into_iter()522			.filter(|(_, (visible, _))| include_hidden || *visible)523			.map(|(k, _)| k)524			.collect();525		fields.sort_unstable();526		fields527	}528	pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec<IStr> {529		self.fields_ex(530			false,531			#[cfg(feature = "exp-preserve-order")]532			preserve_order,533		)534	}535	pub fn values_ex(536		&self,537		include_hidden: bool,538		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,539	) -> ArrValue {540		ArrValue::new(PickObjectValues::new(541			self.clone(),542			self.fields_ex(543				include_hidden,544				#[cfg(feature = "exp-preserve-order")]545				preserve_order,546			),547		))548	}549	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {550		self.values_ex(551			false,552			#[cfg(feature = "exp-preserve-order")]553			preserve_order,554		)555	}556	pub fn key_values_ex(557		&self,558		include_hidden: bool,559		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,560	) -> ArrValue {561		ArrValue::new(PickObjectKeyValues::new(562			self.clone(),563			self.fields_ex(564				include_hidden,565				#[cfg(feature = "exp-preserve-order")]566				preserve_order,567			),568		))569	}570	pub fn key_values(571		&self,572		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,573	) -> ArrValue {574		self.key_values_ex(575			false,576			#[cfg(feature = "exp-preserve-order")]577			preserve_order,578		)579	}580}581582impl OopObject {583	pub fn new(584		sup: Option<ObjValue>,585		this_entries: Cc<GcHashMap<IStr, ObjMember>>,586		assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,587	) -> Self {588		Self {589			sup,590			// this: None,591			assertions,592			assertions_ran: RefCell::new(GcHashSet::new()),593			this_entries,594			value_cache: RefCell::new(GcHashMap::new()),595		}596	}597598	fn evaluate_this(&self, v: &ObjMember, real_this: ObjValue) -> Result<Val> {599		v.invoke.evaluate(self.sup.clone(), Some(real_this))600	}601602	// FIXME: Duplication between ObjValue and OopObject603	fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {604		let mut out = FxHashMap::default();605		self.enum_fields(606			SuperDepth::default(),607			&mut |depth, index, name, visibility| {608				let new_sort_key = FieldSortKey::new(depth, index);609				let entry = out.entry(name);610				let (visible, _) = entry.or_insert((true, new_sort_key));611				match visibility {612					Visibility::Normal => {}613					Visibility::Hidden => {614						*visible = false;615					}616					Visibility::Unhide => {617						*visible = true;618					}619				};620				false621			},622		);623		out624	}625}626627impl ObjectLike for OopObject {628	fn extend_from(&self, sup: ObjValue) -> ObjValue {629		ObjValue::new(match &self.sup {630			None => Self::new(631				Some(sup),632				self.this_entries.clone(),633				self.assertions.clone(),634			),635			Some(v) => Self::new(636				Some(v.extend_from(sup)),637				self.this_entries.clone(),638				self.assertions.clone(),639			),640		})641	}642643	fn len(&self) -> usize {644		// Maybe it will be better to not compute sort key here?645		self.fields_visibility()646			.into_iter()647			.filter(|(_, (visible, _))| *visible)648			.count()649	}650651	/// Returns false only if there is any visible entry.652	///653	/// Note that object with hidden fields `{a:: 1}` will be reported as empty here.654	fn is_empty(&self) -> bool {655		self.len() == 0656	}657658	/// Run callback for every field found in object659	///660	/// Returns true if ended prematurely661	fn enum_fields(&self, depth: SuperDepth, handler: &mut EnumFieldsHandler<'_>) -> bool {662		if let Some(s) = &self.sup {663			if s.enum_fields(depth.deeper(), handler) {664				return true;665			}666		}667		for (name, member) in self.this_entries.iter() {668			if handler(669				depth,670				member.original_index,671				name.clone(),672				member.flags.visibility(),673			) {674				return true;675			}676		}677		false678	}679680	fn has_field_include_hidden(&self, name: IStr) -> bool {681		if self.this_entries.contains_key(&name) {682			true683		} else if let Some(super_obj) = &self.sup {684			super_obj.has_field_include_hidden(name)685		} else {686			false687		}688	}689	fn has_field(&self, name: IStr) -> bool {690		self.field_visibility(name)691			.map_or(false, |v| v.is_visible())692	}693694	fn get_for(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {695		let cache_key = (key.clone(), Some(this.clone().downgrade()));696		if let Some(v) = self.value_cache.borrow().get(&cache_key) {697			return Ok(match v {698				CacheValue::Cached(v) => Some(v.clone()),699				CacheValue::NotFound => None,700				CacheValue::Pending => bail!(InfiniteRecursionDetected),701				CacheValue::Errored(e) => return Err(e.clone()),702			});703		}704		self.value_cache705			.borrow_mut()706			.insert(cache_key.clone(), CacheValue::Pending);707		let value = self.get_for_uncached(key, this).inspect_err(|e| {708			self.value_cache709				.borrow_mut()710				.insert(cache_key.clone(), CacheValue::Errored(e.clone()));711		})?;712		self.value_cache.borrow_mut().insert(713			cache_key,714			value715				.as_ref()716				.map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())),717		);718		Ok(value)719	}720	fn get_for_uncached(&self, key: IStr, real_this: ObjValue) -> Result<Option<Val>> {721		match (self.this_entries.get(&key), &self.sup) {722			(Some(k), None) => Ok(Some(self.evaluate_this(k, real_this)?)),723			(Some(k), Some(super_obj)) => {724				let our = self.evaluate_this(k, real_this.clone())?;725				if k.flags.add() {726					super_obj727						.get_raw(key, real_this)?728						.map_or(Ok(Some(our.clone())), |v| {729							Ok(Some(evaluate_add_op(&v, &our)?))730						})731				} else {732					Ok(Some(our))733				}734			}735			(None, Some(super_obj)) => super_obj.get_raw(key, real_this),736			(None, None) => Ok(None),737		}738	}739	fn field_visibility(&self, name: IStr) -> Option<Visibility> {740		if let Some(m) = self.this_entries.get(&name) {741			Some(match &m.flags.visibility() {742				Visibility::Normal => self743					.sup744					.as_ref()745					.and_then(|super_obj| super_obj.field_visibility(name))746					.unwrap_or(Visibility::Normal),747				v => *v,748			})749		} else if let Some(super_obj) = &self.sup {750			super_obj.field_visibility(name)751		} else {752			None753		}754	}755756	fn run_assertions_raw(&self, real_this: ObjValue) -> Result<()> {757		if self.assertions.is_empty() {758			if let Some(super_obj) = &self.sup {759				super_obj.run_assertions_raw(real_this)?;760			}761			return Ok(());762		}763		if self.assertions_ran.borrow_mut().insert(real_this.clone()) {764			for assertion in self.assertions.iter() {765				if let Err(e) = assertion.run(self.sup.clone(), Some(real_this.clone())) {766					self.assertions_ran.borrow_mut().remove(&real_this);767					return Err(e);768				}769			}770			if let Some(super_obj) = &self.sup {771				super_obj.run_assertions_raw(real_this)?;772			}773		}774		Ok(())775	}776}777778impl PartialEq for ObjValue {779	fn eq(&self, other: &Self) -> bool {780		Cc::ptr_eq(&self.0, &other.0)781	}782}783784impl Eq for ObjValue {}785impl Hash for ObjValue {786	fn hash<H: Hasher>(&self, hasher: &mut H) {787		hasher.write_usize(addr_of!(*self.0) as usize);788	}789}790791#[allow(clippy::module_name_repetitions)]792pub struct ObjValueBuilder {793	sup: Option<ObjValue>,794	map: GcHashMap<IStr, ObjMember>,795	assertions: Vec<TraceBox<dyn ObjectAssertion>>,796	next_field_index: FieldIndex,797}798impl ObjValueBuilder {799	pub fn new() -> Self {800		Self::with_capacity(0)801	}802	pub fn with_capacity(capacity: usize) -> Self {803		Self {804			sup: None,805			map: GcHashMap::with_capacity(capacity),806			assertions: Vec::new(),807			next_field_index: FieldIndex::default(),808		}809	}810	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {811		self.assertions.reserve_exact(capacity);812		self813	}814	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {815		self.sup = Some(super_obj);816		self817	}818819	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {820		self.assertions.push(tb!(assertion));821		self822	}823	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {824		let field_index = self.next_field_index;825		self.next_field_index = self.next_field_index.next();826		ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)827	}828	/// Preset for common method definiton pattern:829	/// Create a hidden field with the function value.830	///831	/// `.field(name).hide().value(Val::function(value))`832	pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {833		self.field(name).hide().value(Val::Func(value.into()));834		self835	}836	pub fn try_method(837		&mut self,838		name: impl Into<IStr>,839		value: impl Into<FuncVal>,840	) -> Result<&mut Self> {841		self.field(name).hide().try_value(Val::Func(value.into()))?;842		Ok(self)843	}844845	pub fn build(self) -> ObjValue {846		if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() {847			return ObjValue::new_empty();848		}849		ObjValue::new(OopObject::new(850			self.sup,851			Cc::new(self.map),852			Cc::new(self.assertions),853		))854	}855}856impl Default for ObjValueBuilder {857	fn default() -> Self {858		Self::with_capacity(0)859	}860}861862#[allow(clippy::module_name_repetitions)]863#[must_use = "value not added unless binding() was called"]864pub struct ObjMemberBuilder<Kind> {865	kind: Kind,866	name: IStr,867	add: bool,868	visibility: Visibility,869	original_index: FieldIndex,870	location: Option<Span>,871}872873#[allow(clippy::missing_const_for_fn)]874impl<Kind> ObjMemberBuilder<Kind> {875	pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self {876		Self {877			kind,878			name,879			original_index,880			add: false,881			visibility: Visibility::Normal,882			location: None,883		}884	}885886	pub const fn with_add(mut self, add: bool) -> Self {887		self.add = add;888		self889	}890	pub fn add(self) -> Self {891		self.with_add(true)892	}893	pub fn with_visibility(mut self, visibility: Visibility) -> Self {894		self.visibility = visibility;895		self896	}897	pub fn hide(self) -> Self {898		self.with_visibility(Visibility::Hidden)899	}900	pub fn with_location(mut self, location: Span) -> Self {901		self.location = Some(location);902		self903	}904	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {905		(906			self.kind,907			self.name,908			ObjMember {909				flags: ObjFieldFlags::new(self.add, self.visibility),910				original_index: self.original_index,911				invoke: binding,912				location: self.location,913			},914		)915	}916}917918pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);919impl ObjMemberBuilder<ValueBuilder<'_>> {920	/// Inserts value, replacing if it is already defined921	pub fn value(self, value: impl Into<Val>) {922		let (receiver, name, member) =923			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));924		let entry = receiver.0.map.entry(name);925		entry.insert(member);926	}927928	/// Tries to insert value, returns an error if it was already defined929	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {930		self.try_thunk(Thunk::evaluated(value.into()))931	}932	pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {933		self.binding(MaybeUnbound::Bound(value.into()))934	}935	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {936		self.binding(MaybeUnbound::Unbound(Cc::new(tb!(bindable))))937	}938	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {939		let (receiver, name, member) = self.build_member(binding);940		let location = member.location.clone();941		let old = receiver.0.map.insert(name.clone(), member);942		if old.is_some() {943			in_frame(944				CallLocation(location.as_ref()),945				|| format!("field <{}> initializtion", name.clone()),946				|| bail!(DuplicateFieldName(name.clone())),947			)?;948		}949		Ok(())950	}951}952953pub struct ExtendBuilder<'v>(&'v mut ObjValue);954impl ObjMemberBuilder<ExtendBuilder<'_>> {955	pub fn value(self, value: impl Into<Val>) {956		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));957	}958	pub fn bindable(self, bindable: TraceBox<dyn Unbound<Bound = Val>>) {959		self.binding(MaybeUnbound::Unbound(Cc::new(bindable)));960	}961	pub fn binding(self, binding: MaybeUnbound) {962		let (receiver, name, member) = self.build_member(binding);963		let new = receiver.0.clone();964		*receiver.0 = new.extend_with_raw_member(name, member);965	}966}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -11,6 +11,7 @@
 use derivative::Derivative;
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::Thunk;
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
@@ -32,6 +33,27 @@
 }
 
 #[derive(Trace)]
+pub struct ThunkValueClosure<D: Trace, O: 'static> {
+	env: D,
+	// Carries no data, as it is not a real closure, all the
+	// captured environment is stored in `env` field.
+	#[trace(skip)]
+	closure: fn(D) -> Result<O>,
+}
+impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {
+	pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {
+		Self { env, closure }
+	}
+}
+impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {
+	type Output = O;
+
+	fn get(self: Box<Self>) -> Result<Self::Output> {
+		(self.closure)(self.env)
+	}
+}
+
+#[derive(Trace)]
 enum ThunkInner<T: Trace> {
 	Computed(T),
 	Errored(Error),
@@ -113,28 +135,11 @@
 		M: ThunkMapper<Input>,
 		M::Output: Trace,
 	{
-		#[derive(Trace)]
-		struct Mapped<Input: Trace, Mapper: Trace> {
-			inner: Thunk<Input>,
-			mapper: Mapper,
-		}
-		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>
-		where
-			Input: Trace + Clone,
-			Mapper: ThunkMapper<Input>,
-		{
-			type Output = Mapper::Output;
-
-			fn get(self: Box<Self>) -> Result<Self::Output> {
-				let value = self.inner.evaluate()?;
-				let mapped = self.mapper.map(value)?;
-				Ok(mapped)
-			}
-		}
-
-		Thunk::new(Mapped::<Input, M> {
-			inner: self,
-			mapper,
+		let inner = self;
+		Thunk!(move || {
+			let value = inner.evaluate()?;
+			let mapped = mapper.map(value)?;
+			Ok(mapped)
 		})
 	}
 }
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()
+}