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

difftreelog

refactor turn Thunk structure around to reduce allocations

ztvvmqptYaroslav Bolyukin2026-02-12parent: #d5d04bb.patch.diff
in: master

9 files changed

modified.gitignorediffbeforeafterboth
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,10 @@
 .vscode
 .direnv
 
+# Nix artifacts
+/result
+/result-*
+
 cache
 
 jsonnet-cpp
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,
-	Context, Error, ObjValue, Result, Thunk, Val,
+	val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,
 };
 
 pub trait ArrayLike: Any + Trace + Debug {
@@ -191,9 +191,25 @@
 			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
 		};
 
-		let arr_thunk = self.clone();
-		Some(Thunk!(move || {
-			arr_thunk.get(index).transpose().expect("index checked")
+		#[derive(Trace)]
+		struct ExprArrThunk {
+			expr: ExprArray,
+			index: usize,
+		}
+		impl ThunkValue for ExprArrThunk {
+			type Output = Val;
+
+			fn get(&self) -> Result<Self::Output> {
+				self.expr
+					.get(self.index)
+					.transpose()
+					.expect("index checked")
+			}
+		}
+
+		Some(Thunk::new(ExprArrThunk {
+			expr: self.clone(),
+			index,
 		}))
 	}
 	fn get_cheap(&self, _index: usize) -> Option<Val> {
@@ -484,9 +500,22 @@
 			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}
 		};
 
-		let arr_thunk = self.clone();
-		Some(Thunk!(move || {
-			arr_thunk.get(index).transpose().expect("index checked")
+		#[derive(Trace)]
+		struct MappedArrayThunk<const WITH_INDEX: bool> {
+			arr: MappedArray<WITH_INDEX>,
+			index: usize,
+		}
+		impl<const WITH_INDEX: bool> ThunkValue for MappedArrayThunk<WITH_INDEX> {
+			type Output = Val;
+
+			fn get(&self) -> Result<Self::Output> {
+				self.arr.get(self.index).transpose().expect("index checked")
+			}
+		}
+
+		Some(Thunk::new(MappedArrayThunk {
+			arr: self.clone(),
+			index,
 		}))
 	}
 
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -40,8 +40,9 @@
 impl<T: Trace + Clone> ThunkValue for Pending<T> {
 	type Output = T;
 
-	fn get(self: Box<Self>) -> Result<Self::Output> {
+	fn get(&self) -> Result<Self::Output> {
 		let Some(value) = self.0.get() else {
+			// TODO: Other error?
 			bail!(InfiniteRecursionDetected);
 		};
 		Ok(value.clone())
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/obj.rs
1use std::{2	any::Any,3	cell::{Cell, RefCell},4	collections::hash_map::Entry,5	fmt::{self, Debug},6	hash::{Hash, Hasher},7	mem,8	num::Saturating,9	ops::ControlFlow,10};1112use educe::Educe;13use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};14use jrsonnet_interner::IStr;15use jrsonnet_parser::{Span, Visibility};16use rustc_hash::{FxHashMap, FxHashSet};1718use crate::{19	arr::{PickObjectKeyValues, PickObjectValues},20	bail,21	error::{suggest_object_fields, ErrorKind::*},22	function::{CallLocation, FuncVal},23	gc::WithCapacityExt as _,24	identity_hash, in_frame,25	operator::evaluate_add_op,26	val::ArrValue,27	CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,28};2930#[cfg(not(feature = "exp-preserve-order"))]31mod ordering {32	#![allow(33		// This module works as stub for preserve-order feature34		clippy::unused_self,35	)]3637	use jrsonnet_gcmodule::Trace;3839	#[derive(Clone, Copy, Default, Debug, Trace)]40	pub struct FieldIndex(());41	impl FieldIndex {42		pub const fn next(self) -> Self {43			Self(())44		}45	}4647	#[derive(Clone, Copy, Default, Debug, Trace)]48	pub struct SuperDepth(());49	impl SuperDepth {50		pub(super) fn deepen(self) {}51	}52}5354#[cfg(feature = "exp-preserve-order")]55mod ordering {56	use std::cmp::Reverse;5758	use jrsonnet_gcmodule::Trace;5960	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]61	pub struct FieldIndex(u32);62	impl FieldIndex {63		pub fn next(self) -> Self {64			Self(self.0 + 1)65		}66	}6768	#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]69	pub struct SuperDepth(u32);70	impl SuperDepth {71		pub(super) fn deepen(&mut self) {72			self.0 += 173		}74	}7576	#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]77	pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);78	impl FieldSortKey {79		pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {80			Self(Reverse(depth), index)81		}82	}83}8485#[cfg(feature = "exp-preserve-order")]86use ordering::FieldSortKey;87use ordering::{FieldIndex, SuperDepth};8889// 0 - add90//  12 - visibility91#[derive(Clone, Copy)]92pub struct ObjFieldFlags(u8);93impl ObjFieldFlags {94	fn new(add: bool, visibility: Visibility) -> Self {95		let mut v = 0;96		if add {97			v |= 1;98		}99		v |= match visibility {100			Visibility::Normal => 0b000,101			Visibility::Hidden => 0b010,102			Visibility::Unhide => 0b100,103		};104		Self(v)105	}106	pub fn add(&self) -> bool {107		self.0 & 1 != 0108	}109	pub fn visibility(&self) -> Visibility {110		match (self.0 & 0b110) >> 1 {111			0b00 => Visibility::Normal,112			0b01 => Visibility::Hidden,113			0b10 => Visibility::Unhide,114			_ => unreachable!(),115		}116	}117}118impl Debug for ObjFieldFlags {119	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {120		f.debug_struct("ObjFieldFlags")121			.field("add", &self.add())122			.field("visibility", &self.visibility())123			.finish()124	}125}126127#[allow(clippy::module_name_repetitions)]128#[derive(Debug, Trace)]129pub struct ObjMember {130	#[trace(skip)]131	flags: ObjFieldFlags,132	original_index: FieldIndex,133	pub invoke: MaybeUnbound,134	pub location: Option<Span>,135}136137cc_dyn!(CcObjectAssertion, ObjectAssertion);138pub trait ObjectAssertion: Trace {139	fn run(&self, sup_this: SupThis) -> Result<()>;140}141142// Field => This143144#[derive(Trace, Debug)]145enum CacheValue {146	Cached(Result<Option<Val>>),147	Pending,148}149150#[allow(clippy::module_name_repetitions)]151#[derive(Trace, Default)]152#[trace(tracking(force))]153pub struct OopObject {154	assertions: Vec<CcObjectAssertion>,155	this_entries: FxHashMap<IStr, ObjMember>,156}157impl Debug for OopObject {158	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {159		f.debug_struct("OopObject")160			.field("this_entries", &self.this_entries)161			.finish_non_exhaustive()162	}163}164impl OopObject {165	fn is_empty(&self) -> bool {166		self.assertions.is_empty() && self.this_entries.is_empty()167	}168}169170type EnumFieldsHandler<'a> =171	dyn FnMut(SuperDepth, FieldIndex, IStr, EnumFields) -> ControlFlow<()> + 'a;172173pub enum EnumFields {174	Normal(Visibility),175	Omit(Skip),176}177178#[derive(Trace, Clone)]179pub enum GetFor {180	// Return value181	Final(Val),182	// Continue iterating over cores, add current value to sum stack183	SuperPlus(Val),184	// Ignore the field value, stop at this layer instead185	Omit(#[trace(skip)] Skip),186	NotFound,187}188189#[derive(Acyclic, Clone)]190pub enum FieldVisibility {191	Found(Visibility),192	Omit(Skip),193	NotFound,194}195196#[derive(Acyclic, Clone)]197pub enum HasFieldIncludeHidden {198	Exists,199	NotFound,200	Omit(Skip),201}202203type Skip = Saturating<usize>;204205pub trait ObjectCore: Trace + Any + Debug {206	// If callback returns false, iteration stops, and this call returns false.207	fn enum_fields_core(208		&self,209		super_depth: &mut SuperDepth,210		handler: &mut EnumFieldsHandler<'_>,211	) -> bool;212213	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden;214215	fn get_for_core(&self, key: IStr, sup_this: SupThis, omit_only: bool) -> Result<GetFor>;216	fn field_visibility_core(&self, field: IStr) -> FieldVisibility;217218	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()>;219}220221#[derive(Clone, Trace)]222pub struct WeakObjValue(#[trace(skip)] Weak<ObjValueInner>);223impl Debug for WeakObjValue {224	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {225		f.debug_tuple("WeakObjValue").finish()226	}227}228229impl PartialEq for WeakObjValue {230	fn eq(&self, other: &Self) -> bool {231		Weak::ptr_eq(&self.0, &other.0)232	}233}234235impl Eq for WeakObjValue {}236impl Hash for WeakObjValue {237	fn hash<H: Hasher>(&self, hasher: &mut H) {238		// Safety: usize is POD239		let addr = unsafe { *std::ptr::addr_of!(self.0).cast() };240		hasher.write_usize(addr);241	}242}243244cc_dyn!(245	#[derive(Clone, Debug)]246	CcObjectCore, ObjectCore,247	pub fn new() {...}248);249#[derive(Trace, Educe)]250#[educe(Debug)]251struct ObjValueInner {252	cores: Vec<CcObjectCore>,253	assertions_ran: Cell<bool>,254	value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,255}256257thread_local! {258	static RUNNING_ASSERTIONS: RefCell<FxHashSet<ObjValue>> = RefCell::default();259}260fn is_asserting(obj: &ObjValue) -> bool {261	RUNNING_ASSERTIONS.with_borrow(|v| v.contains(obj))262}263/// Returns false if already asserting264fn start_asserting(obj: &ObjValue) -> bool {265	RUNNING_ASSERTIONS.with_borrow_mut(|v| v.insert(obj.clone()))266}267fn finish_asserting(obj: &ObjValue) {268	RUNNING_ASSERTIONS.with_borrow_mut(|v| {269		let r = v.remove(obj);270		debug_assert!(271			r,272			"finish_asserting was called before start_asserting or twice"273		);274	});275}276277thread_local! {278	static EMPTY_OBJ: ObjValue = ObjValue(Cc::new(ObjValueInner {279		cores: vec![],280		assertions_ran: Cell::new(true),281		value_cache: Default::default(),282	}))283}284285#[allow(clippy::module_name_repetitions)]286#[derive(Clone, Trace, Debug, Educe)]287#[educe(PartialEq, Hash, Eq)]288pub struct ObjValue(289	#[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc<ObjValueInner>,290);291292impl ObjValue {293	pub fn empty() -> Self {294		EMPTY_OBJ.with(|v| v.clone())295	}296	pub fn is_empty(&self) -> bool {297		self.0.cores.is_empty() || self.len() == 0298	}299}300301#[derive(Trace, Debug)]302struct StandaloneSuperCore {303	sup: CoreIdx,304	this: ObjValue,305}306impl ObjectCore for StandaloneSuperCore {307	fn enum_fields_core(308		&self,309		super_depth: &mut SuperDepth,310		handler: &mut EnumFieldsHandler<'_>,311	) -> bool {312		self.this.enum_fields_idx(super_depth, handler, self.sup)313	}314315	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {316		if self.this.has_field_include_hidden_idx(name, self.sup) {317			HasFieldIncludeHidden::Exists318		} else {319			HasFieldIncludeHidden::NotFound320		}321	}322323	fn get_for_core(&self, key: IStr, _sup_this: SupThis, omit_only: bool) -> Result<GetFor> {324		if omit_only {325			return Ok(GetFor::NotFound);326		}327		let v = self.this.get_idx(key, self.sup)?;328		Ok(v.map_or(GetFor::NotFound, |v| GetFor::Final(v)))329	}330331	fn field_visibility_core(&self, field: IStr) -> FieldVisibility {332		match self.this.field_visibility_idx(field, self.sup) {333			Some(c) => FieldVisibility::Found(c),334			None => FieldVisibility::NotFound,335		}336	}337338	fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {339		self.this.run_assertions()340	}341}342343#[derive(Debug, Acyclic)]344struct OmitFieldsCore {345	omit: FxHashSet<IStr>,346	prev_layers: usize,347}348impl ObjectCore for OmitFieldsCore {349	fn enum_fields_core(350		&self,351		super_depth: &mut SuperDepth,352		handler: &mut EnumFieldsHandler<'_>,353	) -> bool {354		let mut fi = FieldIndex::default();355		for f in &self.omit {356			if let ControlFlow::Break(()) = handler(357				*super_depth,358				fi,359				f.clone(),360				EnumFields::Omit(Saturating(self.prev_layers)),361			) {362				return false;363			}364			fi = fi.next();365		}366		true367	}368369	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {370		if self.omit.contains(&name) {371			return HasFieldIncludeHidden::Omit(Saturating(self.prev_layers));372		}373		HasFieldIncludeHidden::NotFound374	}375376	fn get_for_core(&self, key: IStr, _sup_this: SupThis, _omit_only: bool) -> Result<GetFor> {377		if self.omit.contains(&key) {378			return Ok(GetFor::Omit(Saturating(self.prev_layers)));379		}380		Ok(GetFor::NotFound)381	}382383	fn field_visibility_core(&self, field: IStr) -> FieldVisibility {384		if self.omit.contains(&field) {385			return FieldVisibility::Omit(Saturating(self.prev_layers));386		}387		FieldVisibility::NotFound388	}389390	fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {391		Ok(())392	}393}394395#[derive(Hash, PartialEq, Eq, Trace, Clone, Copy, Debug)]396struct CoreIdx {397	idx: usize,398}399impl CoreIdx {400	fn super_exists(self) -> bool {401		self.idx != 0402	}403}404#[derive(Trace, Clone, PartialEq, Eq, Hash, Debug)]405pub struct SupThis {406	sup: CoreIdx,407	this: ObjValue,408}409impl SupThis {410	pub fn has_super(&self) -> bool {411		self.sup.super_exists()412	}413	/// Implementation of `"field" in super` operation,414	/// works faster than standalone super path.415	///416	/// In case of no `super` existence, returns false.417	pub fn field_in_super(&self, field: IStr) -> bool {418		self.this.has_field_include_hidden_idx(field, self.sup)419	}420	/// Implementation of `super.field` operation,421	/// works faster than standalone super path.422	///423	/// In case of no `super` existence, returns `NoSuperFound`424	pub fn get_super(&self, field: IStr) -> Result<Option<Val>> {425		if !self.sup.super_exists() {426			bail!(NoSuperFound);427		}428		self.this.get_idx(field, self.sup)429	}430	/// `super` with `self` overriden for top-level lookups.431	/// Exists when super appears outside of `super.field`/`"field" in super` expressions432	/// Exclusive to jrsonnet.433	///434	/// Might return `NoSuperFound` error.435	pub fn standalone_super(&self) -> Result<ObjValue> {436		if !self.sup.super_exists() {437			bail!(NoSuperFound)438		}439		let mut out = ObjValue::builder();440		out.reserve_cores(1).extend_with_core(StandaloneSuperCore {441			sup: self.sup,442			this: self.this.clone(),443		});444		Ok(out.build())445	}446	pub fn this(&self) -> &ObjValue {447		&self.this448	}449	pub fn downgrade(self) -> WeakSupThis {450		WeakSupThis {451			sup: self.sup,452			this: self.this.downgrade(),453		}454	}455}456#[derive(Trace, PartialEq, Eq, Hash, Debug)]457pub struct WeakSupThis {458	sup: CoreIdx,459	this: WeakObjValue,460}461462impl ObjValue {463	pub fn builder() -> ObjValueBuilder {464		ObjValueBuilder::new()465	}466	pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {467		ObjValueBuilder::with_capacity(capacity)468	}469	pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {470		let mut out = ObjValueBuilder::with_capacity(1);471		out.with_super(self);472		let mut member = out.field(key);473		if value.flags.add() {474			member = member.add();475		}476		if let Some(loc) = value.location {477			member = member.with_location(loc);478		}479		let _ = member480			.with_visibility(value.flags.visibility())481			.binding(value.invoke);482		out.build()483	}484	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {485		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())486	}487488	pub fn extend(&mut self) -> ObjValueBuilder {489		let mut out = ObjValueBuilder::new();490		out.with_super(self.clone());491		out492	}493494	#[must_use]495	pub fn extend_from(&self, sup: Self) -> Self {496		let mut cores = sup.0.cores.clone();497		cores.extend(self.0.cores.iter().cloned());498		ObjValue(Cc::new(ObjValueInner {499			cores,500			value_cache: RefCell::default(),501			assertions_ran: Cell::new(false),502		}))503	}504	// #[must_use]505	// pub fn with_this(&self, this: Self) -> Self {506	// 	self.0.with_this(self.clone(), this)507	// }508	/// Returns amount of visible object fields509	/// If object only contains hidden fields - may return zero.510	pub fn len(&self) -> usize {511		self.fields_visibility()512			.values()513			.filter(|d| d.visible())514			.count()515	}516	/// For each field, calls callback.517	/// If callback returns false - ends iteration prematurely.518	///519	/// Returns false if ended prematurely520	pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool {521		let mut super_depth = SuperDepth::default();522		self.enum_fields_idx(523			&mut super_depth,524			handler,525			CoreIdx {526				idx: self.0.cores.len(),527			},528		)529	}530	fn enum_fields_idx(531		&self,532		super_depth: &mut SuperDepth,533		handler: &mut EnumFieldsHandler<'_>,534		idx: CoreIdx,535	) -> bool {536		for core in self.0.cores[..idx.idx].iter().rev() {537			if !core.0.enum_fields_core(super_depth, handler) {538				return false;539			}540			super_depth.deepen();541		}542		true543	}544545	pub fn has_field_include_hidden(&self, name: IStr) -> bool {546		self.has_field_include_hidden_idx(547			name,548			CoreIdx {549				idx: self.0.cores.len(),550			},551		)552	}553	fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {554		let mut skip = Saturating(0usize);555		for ele in self.0.cores[..core.idx].iter().rev() {556			match ele.0.has_field_include_hidden_core(name.clone()) {557				HasFieldIncludeHidden::Exists => {558					if skip.0 == 0 {559						return true;560					}561				}562				HasFieldIncludeHidden::Omit(new_skip) => {563					// +1 including this core564					skip = skip.max(new_skip + Saturating(1));565				}566				HasFieldIncludeHidden::NotFound => {}567			}568			skip -= 1;569		}570		false571	}572	pub fn has_field(&self, name: IStr) -> bool {573		match self.field_visibility(name) {574			Some(Visibility::Unhide | Visibility::Normal) => true,575			Some(Visibility::Hidden) | None => false,576		}577	}578	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {579		if include_hidden {580			self.has_field_include_hidden(name)581		} else {582			self.has_field(name)583		}584	}585	pub fn get(&self, key: IStr) -> Result<Option<Val>> {586		self.get_idx(587			key,588			CoreIdx {589				idx: self.0.cores.len(),590			},591		)592	}593594	fn get_idx(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {595		let cache_key = (key.clone(), core);596		{597			let mut cache = self.0.value_cache.borrow_mut();598			// entry_ref candidate?599			match cache.entry(cache_key.clone()) {600				Entry::Occupied(v) => match v.get() {601					CacheValue::Cached(v) => return v.clone(),602					CacheValue::Pending => {603						if !is_asserting(self) {604							bail!(InfiniteRecursionDetected);605						}606					}607				},608				Entry::Vacant(v) => {609					v.insert(CacheValue::Pending);610				}611			};612		}613		let result = self.get_idx_uncached(key, core);614		{615			let mut cache = self.0.value_cache.borrow_mut();616			cache.insert(cache_key, CacheValue::Cached(result.clone()));617		}618		result619	}620	fn get_idx_uncached(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {621		self.run_assertions()?;622		let mut add_stack = Vec::with_capacity(2);623		let mut skip = Saturating(0);624		for (sup, core) in self.0.cores[..core.idx].iter().enumerate().rev() {625			let sup_this = SupThis {626				sup: CoreIdx { idx: sup },627				this: self.clone(),628			};629			match core.0.get_for_core(key.clone(), sup_this, skip.0 != 0)? {630				GetFor::Final(val) if add_stack.is_empty() => {631					if skip.0 == 0 {632						return Ok(Some(val));633					}634				}635				GetFor::Final(val) => {636					if skip.0 == 0 {637						add_stack.push(val);638						break;639					}640				}641				GetFor::SuperPlus(val) => {642					if skip.0 == 0 {643						add_stack.push(val);644					}645				}646				GetFor::Omit(new_skip) => {647					// +1 including this core648					skip = skip.max(new_skip + Saturating(1));649				}650				GetFor::NotFound => {}651			}652			skip -= 1;653		}654		if add_stack.is_empty() {655			// None of layers had this field656			return Ok(None);657		} else if add_stack.len() == 1 {658			// A layer had this field, but it wanted this field to be added with super.659			// However, no super had this field, fail-safe660			return Ok(Some(add_stack.pop().expect("single element on stack")));661		}662		let mut values = add_stack.into_iter().rev();663		let init = values.next().expect("at least 2 elements");664665		values666			.try_fold(init, |a, b| evaluate_add_op(&a, &b))667			.map(Some)668669		// self.0.get_raw(key, this)670	}671672	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {673		let Some(value) = self.get(key.clone())? else {674			let suggestions = suggest_object_fields(self, key.clone());675			bail!(NoSuchField(key, suggestions))676		};677		Ok(value)678	}679680	fn field_visibility(&self, field: IStr) -> Option<Visibility> {681		self.field_visibility_idx(682			field,683			CoreIdx {684				idx: self.0.cores.len(),685			},686		)687	}688	fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {689		let mut exists = false;690		let mut skip = Saturating(0usize);691		for ele in self.0.cores[..core.idx].iter().rev() {692			let vis = ele.0.field_visibility_core(field.clone());693			match vis {694				FieldVisibility::Found(vis @ (Visibility::Unhide | Visibility::Hidden)) => {695					if skip.0 == 0 {696						return Some(vis);697					}698				}699				FieldVisibility::Found(Visibility::Normal) => {700					if skip.0 == 0 {701						exists = true702					}703				}704				FieldVisibility::NotFound => {}705				FieldVisibility::Omit(new_skip) => {706					// +1 including this core707					skip = skip.max(new_skip + Saturating(1));708				}709			}710			skip -= 1;711		}712		exists.then_some(Visibility::Normal)713	}714715	pub fn run_assertions(&self) -> Result<()> {716		if self.0.assertions_ran.get() {717			return Ok(());718		}719		if !start_asserting(self) {720			return Ok(());721		}722		for (idx, ele) in self.0.cores.iter().enumerate() {723			let sup_this = SupThis {724				sup: CoreIdx { idx },725				this: self.clone(),726			};727			ele.0.run_assertions_core(sup_this).inspect_err(|_e| {728				finish_asserting(self);729			})?;730		}731		finish_asserting(self);732		self.0.assertions_ran.set(true);733		Ok(())734	}735736	pub fn iter(737		&self,738		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,739	) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {740		let fields = self.fields(741			#[cfg(feature = "exp-preserve-order")]742			preserve_order,743		);744		fields.into_iter().map(|field| {745			(746				field.clone(),747				self.get(field)748					.map(|opt| opt.expect("iterating over keys, field exists")),749			)750		})751	}752	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {753		if !self.has_field_ex(key.clone(), true) {754			return None;755		}756		let obj = self.clone();757758		Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))759	}760	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {761		let obj = self.clone();762		Thunk!(move || obj.get_or_bail(key))763	}764	pub fn ptr_eq(a: &Self, b: &Self) -> bool {765		Cc::ptr_eq(&a.0, &b.0)766	}767	pub fn downgrade(self) -> WeakObjValue {768		WeakObjValue(self.0.downgrade())769	}770}771772#[derive(Debug)]773struct FieldVisibilityData {774	omitted_until: Saturating<usize>,775	exists_visible: Option<Visibility>,776	#[cfg(feature = "exp-preserve-order")]777	key: FieldSortKey,778}779impl FieldVisibilityData {780	fn visible(&self) -> bool {781		self.exists_visible782			.expect("non-existing fields shall be dropped at the end of fn fields_visibility()")783			.is_visible()784	}785	#[cfg(feature = "exp-preserve-order")]786	fn sort_key(&self) -> FieldSortKey {787		self.key788	}789}790791impl ObjValue {792	fn fields_visibility(&self) -> FxHashMap<IStr, FieldVisibilityData> {793		let mut out = FxHashMap::default();794795		let mut super_depth = SuperDepth::default();796		let mut omit_index = Saturating(0);797		for core in self.0.cores.iter().rev() {798			core.0799				.enum_fields_core(&mut super_depth, &mut |_depth, _index, name, visibility| {800					let entry = out.entry(name);801					let data = entry.or_insert(FieldVisibilityData {802						exists_visible: None,803						#[cfg(feature = "exp-preserve-order")]804						key: FieldSortKey::new(_depth, _index),805						omitted_until: omit_index,806					});807					match visibility {808						EnumFields::Omit(new_skip) => {809							// +1 including this core810							data.omitted_until = data811								.omitted_until812								.max(omit_index + new_skip + Saturating(1));813						}814						EnumFields::Normal(Visibility::Normal) => {815							if data.omitted_until <= omit_index {816								if data.exists_visible.is_none() {817									data.exists_visible = Some(Visibility::Normal);818								}819							}820						}821						EnumFields::Normal(Visibility::Hidden) => {822							if data.omitted_until <= omit_index {823								data.exists_visible = Some(match data.exists_visible {824									// We're iterating in reverse, later unhide is preserved825									Some(Visibility::Unhide) => Visibility::Unhide,826									_ => Visibility::Hidden,827								});828							}829						}830						EnumFields::Normal(Visibility::Unhide) => {831							if data.omitted_until <= omit_index {832								data.exists_visible = Some(match data.exists_visible {833									// We're iterating in reverse, later hide is preserved834									Some(Visibility::Hidden) => Visibility::Hidden,835									_ => Visibility::Unhide,836								});837							}838						}839					};840					return ControlFlow::Continue(());841				});842843			super_depth.deepen();844			omit_index += 1;845		}846847		out.retain(|_, v| v.exists_visible.is_some());848849		out850	}851	pub fn fields_ex(852		&self,853		include_hidden: bool,854		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,855	) -> Vec<IStr> {856		#[cfg(feature = "exp-preserve-order")]857		if preserve_order {858			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self859				.fields_visibility()860				.into_iter()861				.filter(|(_, d)| include_hidden || d.visible())862				.enumerate()863				.map(|(idx, (k, d))| (k, (d.sort_key(), idx)))864				.unzip();865			keys.sort_unstable_by_key(|v| v.0);866			// Reorder in-place by resulting indexes867			for i in 0..fields.len() {868				let x = fields[i].clone();869				let mut j = i;870				loop {871					let k = keys[j].1;872					keys[j].1 = j;873					if k == i {874						break;875					}876					fields[j] = fields[k].clone();877					j = k;878				}879				fields[j] = x;880			}881			return fields;882		}883884		let mut fields: Vec<_> = self885			.fields_visibility()886			.into_iter()887			.filter(|(_, d)| include_hidden || d.visible())888			.map(|(k, _)| k)889			.collect();890		fields.sort_unstable();891		fields892	}893	pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec<IStr> {894		self.fields_ex(895			false,896			#[cfg(feature = "exp-preserve-order")]897			preserve_order,898		)899	}900	pub fn values_ex(901		&self,902		include_hidden: bool,903		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,904	) -> ArrValue {905		ArrValue::new(PickObjectValues::new(906			self.clone(),907			self.fields_ex(908				include_hidden,909				#[cfg(feature = "exp-preserve-order")]910				preserve_order,911			),912		))913	}914	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {915		self.values_ex(916			false,917			#[cfg(feature = "exp-preserve-order")]918			preserve_order,919		)920	}921	pub fn key_values_ex(922		&self,923		include_hidden: bool,924		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,925	) -> ArrValue {926		ArrValue::new(PickObjectKeyValues::new(927			self.clone(),928			self.fields_ex(929				include_hidden,930				#[cfg(feature = "exp-preserve-order")]931				preserve_order,932			),933		))934	}935	pub fn key_values(936		&self,937		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,938	) -> ArrValue {939		self.key_values_ex(940			false,941			#[cfg(feature = "exp-preserve-order")]942			preserve_order,943		)944	}945}946947impl OopObject {948	pub fn new(949		this_entries: FxHashMap<IStr, ObjMember>,950		assertions: Vec<CcObjectAssertion>,951	) -> Self {952		Self {953			this_entries,954			assertions,955		}956	}957}958959impl ObjectCore for OopObject {960	fn enum_fields_core(961		&self,962		super_depth: &mut SuperDepth,963		handler: &mut EnumFieldsHandler<'_>,964	) -> bool {965		for (name, member) in self.this_entries.iter() {966			if matches!(967				handler(968					*super_depth,969					member.original_index,970					name.clone(),971					EnumFields::Normal(member.flags.visibility()),972				),973				ControlFlow::Break(())974			) {975				return false;976			}977		}978		true979	}980981	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {982		if self.this_entries.contains_key(&name) {983			HasFieldIncludeHidden::Exists984		} else {985			HasFieldIncludeHidden::NotFound986		}987	}988989	fn get_for_core(&self, key: IStr, sup_this: SupThis, omit_only: bool) -> Result<GetFor> {990		if omit_only {991			return Ok(GetFor::NotFound);992		}993		match self.this_entries.get(&key) {994			Some(k) => {995				let v = k.invoke.evaluate(sup_this)?;996				Ok(if k.flags.add() {997					GetFor::SuperPlus(v)998				} else {999					GetFor::Final(v)1000				})1001			}1002			None => Ok(GetFor::NotFound),1003		}1004	}1005	fn field_visibility_core(&self, name: IStr) -> FieldVisibility {1006		match self.this_entries.get(&name) {1007			Some(f) => FieldVisibility::Found(f.flags.visibility()),1008			None => FieldVisibility::NotFound,1009		}1010	}10111012	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {1013		if self.assertions.is_empty() {1014			return Ok(());1015		}1016		for assertion in self.assertions.iter() {1017			assertion.0.run(sup_this.clone())?;1018		}1019		Ok(())1020	}1021}10221023#[allow(clippy::module_name_repetitions)]1024pub struct ObjValueBuilder {1025	sup: Vec<CcObjectCore>,10261027	new: OopObject,1028	next_field_index: FieldIndex,1029}1030impl ObjValueBuilder {1031	pub fn new() -> Self {1032		Self::with_capacity(0)1033	}1034	pub fn with_capacity(capacity: usize) -> Self {1035		Self {1036			sup: vec![],1037			new: OopObject {1038				assertions: vec![],1039				this_entries: FxHashMap::with_capacity(capacity),1040			},1041			next_field_index: FieldIndex::default(),1042		}1043	}1044	pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {1045		self.sup.reserve_exact(capacity);1046		self1047	}1048	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {1049		self.new.assertions.reserve_exact(capacity);1050		self1051	}1052	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {1053		self.sup = super_obj.0.cores.clone();1054		self1055	}10561057	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {1058		self.new.assertions.push(CcObjectAssertion::new(assertion));1059		self1060	}1061	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {1062		let field_index = self.next_field_index;1063		self.next_field_index = self.next_field_index.next();1064		ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)1065	}1066	/// Preset for common method definiton pattern:1067	/// Create a hidden field with the function value.1068	///1069	/// `.field(name).hide().value(Val::function(value))`1070	pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {1071		self.field(name).hide().value(Val::Func(value.into()));1072		self1073	}1074	pub fn try_method(1075		&mut self,1076		name: impl Into<IStr>,1077		value: impl Into<FuncVal>,1078	) -> Result<&mut Self> {1079		self.field(name).hide().try_value(Val::Func(value.into()))?;1080		Ok(self)1081	}10821083	pub fn extend_with_core(&mut self, core: impl ObjectCore) {1084		self.commit();1085		self.sup.push(CcObjectCore::new(core));1086	}10871088	fn commit(&mut self) {1089		if !self.new.is_empty() {1090			self.sup.push(CcObjectCore::new(mem::take(&mut self.new)));1091		}1092		self.next_field_index = FieldIndex::default();1093	}10941095	pub fn with_fields_omitted(&mut self, omit: FxHashSet<IStr>) {1096		self.commit();1097		self.sup.push(CcObjectCore::new(OmitFieldsCore {1098			omit,1099			prev_layers: self.sup.len(),1100		}));1101	}11021103	pub fn build(mut self) -> ObjValue {1104		self.commit();1105		if self.sup.is_empty() {1106			return ObjValue::empty();1107		}1108		ObjValue(Cc::new(ObjValueInner {1109			cores: self.sup,1110			assertions_ran: Cell::new(false),1111			value_cache: Default::default(),1112		}))1113	}1114}1115impl Default for ObjValueBuilder {1116	fn default() -> Self {1117		Self::with_capacity(0)1118	}1119}11201121#[allow(clippy::module_name_repetitions)]1122#[must_use = "value not added unless binding() was called"]1123pub struct ObjMemberBuilder<Kind> {1124	kind: Kind,1125	name: IStr,1126	add: bool,1127	visibility: Visibility,1128	original_index: FieldIndex,1129	location: Option<Span>,1130}11311132#[allow(clippy::missing_const_for_fn)]1133impl<Kind> ObjMemberBuilder<Kind> {1134	pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self {1135		Self {1136			kind,1137			name,1138			original_index,1139			add: false,1140			visibility: Visibility::Normal,1141			location: None,1142		}1143	}11441145	pub const fn with_add(mut self, add: bool) -> Self {1146		self.add = add;1147		self1148	}1149	pub fn add(self) -> Self {1150		self.with_add(true)1151	}1152	pub fn with_visibility(mut self, visibility: Visibility) -> Self {1153		self.visibility = visibility;1154		self1155	}1156	pub fn hide(self) -> Self {1157		self.with_visibility(Visibility::Hidden)1158	}1159	pub fn with_location(mut self, location: Span) -> Self {1160		self.location = Some(location);1161		self1162	}1163	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {1164		(1165			self.kind,1166			self.name,1167			ObjMember {1168				flags: ObjFieldFlags::new(self.add, self.visibility),1169				original_index: self.original_index,1170				invoke: binding,1171				location: self.location,1172			},1173		)1174	}1175}11761177pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);1178impl ObjMemberBuilder<ValueBuilder<'_>> {1179	/// Inserts value, replacing if it is already defined1180	pub fn value(self, value: impl Into<Val>) {1181		let (receiver, name, member) =1182			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1183		let entry = receiver.0.new.this_entries.entry(name);1184		entry.insert_entry(member);1185	}1186	/// Inserts thunk, replacing if it is already defined1187	pub fn thunk(self, value: impl Into<Thunk<Val>>) {1188		let (receiver, name, member) = self.build_member(MaybeUnbound::Bound(value.into()));1189		let entry = receiver.0.new.this_entries.entry(name);1190		entry.insert_entry(member);1191	}11921193	/// Tries to insert value, returns an error if it was already defined1194	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {1195		self.try_thunk(Thunk::evaluated(value.into()))1196	}1197	pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {1198		self.binding(MaybeUnbound::Bound(value.into()))1199	}1200	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {1201		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)))1202	}1203	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {1204		let (receiver, name, member) = self.build_member(binding);1205		let location = member.location.clone();1206		let old = receiver.0.new.this_entries.insert(name.clone(), member);1207		if old.is_some() {1208			in_frame(1209				CallLocation(location.as_ref()),1210				|| format!("field <{}> initializtion", name.clone()),1211				|| bail!(DuplicateFieldName(name.clone())),1212			)?;1213		}1214		Ok(())1215	}1216}12171218pub struct ExtendBuilder<'v>(&'v mut ObjValue);1219impl ObjMemberBuilder<ExtendBuilder<'_>> {1220	pub fn value(self, value: impl Into<Val>) {1221		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1222	}1223	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) {1224		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)));1225	}1226	pub fn binding(self, binding: MaybeUnbound) {1227		let (receiver, name, member) = self.build_member(binding);1228		let new = receiver.0.clone();1229		*receiver.0 = new.extend_with_raw_member(name, member);1230	}1231}
after · crates/jrsonnet-evaluator/src/obj.rs
1use std::{2	any::Any,3	cell::{Cell, RefCell},4	collections::hash_map::Entry,5	fmt::{self, Debug},6	hash::{Hash, Hasher},7	mem,8	num::Saturating,9	ops::ControlFlow,10};1112use educe::Educe;13use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};14use jrsonnet_interner::IStr;15use jrsonnet_parser::{Span, Visibility};16use rustc_hash::{FxHashMap, FxHashSet};1718use crate::{19	arr::{PickObjectKeyValues, PickObjectValues},20	bail,21	error::{suggest_object_fields, ErrorKind::*},22	function::{CallLocation, FuncVal},23	gc::WithCapacityExt as _,24	identity_hash, in_frame,25	operator::evaluate_add_op,26	val::{ArrValue, ThunkValue},27	CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,28};2930#[cfg(not(feature = "exp-preserve-order"))]31mod ordering {32	#![allow(33		// This module works as stub for preserve-order feature34		clippy::unused_self,35	)]3637	use jrsonnet_gcmodule::Trace;3839	#[derive(Clone, Copy, Default, Debug, Trace)]40	pub struct FieldIndex(());41	impl FieldIndex {42		pub const fn next(self) -> Self {43			Self(())44		}45	}4647	#[derive(Clone, Copy, Default, Debug, Trace)]48	pub struct SuperDepth(());49	impl SuperDepth {50		pub(super) fn deepen(self) {}51	}52}5354#[cfg(feature = "exp-preserve-order")]55mod ordering {56	use std::cmp::Reverse;5758	use jrsonnet_gcmodule::Trace;5960	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]61	pub struct FieldIndex(u32);62	impl FieldIndex {63		pub fn next(self) -> Self {64			Self(self.0 + 1)65		}66	}6768	#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]69	pub struct SuperDepth(u32);70	impl SuperDepth {71		pub(super) fn deepen(&mut self) {72			self.0 += 173		}74	}7576	#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]77	pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);78	impl FieldSortKey {79		pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {80			Self(Reverse(depth), index)81		}82	}83}8485#[cfg(feature = "exp-preserve-order")]86use ordering::FieldSortKey;87use ordering::{FieldIndex, SuperDepth};8889// 0 - add90//  12 - visibility91#[derive(Clone, Copy)]92pub struct ObjFieldFlags(u8);93impl ObjFieldFlags {94	fn new(add: bool, visibility: Visibility) -> Self {95		let mut v = 0;96		if add {97			v |= 1;98		}99		v |= match visibility {100			Visibility::Normal => 0b000,101			Visibility::Hidden => 0b010,102			Visibility::Unhide => 0b100,103		};104		Self(v)105	}106	pub fn add(&self) -> bool {107		self.0 & 1 != 0108	}109	pub fn visibility(&self) -> Visibility {110		match (self.0 & 0b110) >> 1 {111			0b00 => Visibility::Normal,112			0b01 => Visibility::Hidden,113			0b10 => Visibility::Unhide,114			_ => unreachable!(),115		}116	}117}118impl Debug for ObjFieldFlags {119	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {120		f.debug_struct("ObjFieldFlags")121			.field("add", &self.add())122			.field("visibility", &self.visibility())123			.finish()124	}125}126127#[allow(clippy::module_name_repetitions)]128#[derive(Debug, Trace)]129pub struct ObjMember {130	#[trace(skip)]131	flags: ObjFieldFlags,132	original_index: FieldIndex,133	pub invoke: MaybeUnbound,134	pub location: Option<Span>,135}136137cc_dyn!(CcObjectAssertion, ObjectAssertion);138pub trait ObjectAssertion: Trace {139	fn run(&self, sup_this: SupThis) -> Result<()>;140}141142// Field => This143144#[derive(Trace, Debug)]145enum CacheValue {146	Cached(Result<Option<Val>>),147	Pending,148}149150#[allow(clippy::module_name_repetitions)]151#[derive(Trace, Default)]152#[trace(tracking(force))]153pub struct OopObject {154	assertions: Vec<CcObjectAssertion>,155	this_entries: FxHashMap<IStr, ObjMember>,156}157impl Debug for OopObject {158	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {159		f.debug_struct("OopObject")160			.field("this_entries", &self.this_entries)161			.finish_non_exhaustive()162	}163}164impl OopObject {165	fn is_empty(&self) -> bool {166		self.assertions.is_empty() && self.this_entries.is_empty()167	}168}169170type EnumFieldsHandler<'a> =171	dyn FnMut(SuperDepth, FieldIndex, IStr, EnumFields) -> ControlFlow<()> + 'a;172173pub enum EnumFields {174	Normal(Visibility),175	Omit(Skip),176}177178#[derive(Trace, Clone)]179pub enum GetFor {180	// Return value181	Final(Val),182	// Continue iterating over cores, add current value to sum stack183	SuperPlus(Val),184	// Ignore the field value, stop at this layer instead185	Omit(#[trace(skip)] Skip),186	NotFound,187}188189#[derive(Acyclic, Clone)]190pub enum FieldVisibility {191	Found(Visibility),192	Omit(Skip),193	NotFound,194}195196#[derive(Acyclic, Clone)]197pub enum HasFieldIncludeHidden {198	Exists,199	NotFound,200	Omit(Skip),201}202203type Skip = Saturating<usize>;204205pub trait ObjectCore: Trace + Any + Debug {206	// If callback returns false, iteration stops, and this call returns false.207	fn enum_fields_core(208		&self,209		super_depth: &mut SuperDepth,210		handler: &mut EnumFieldsHandler<'_>,211	) -> bool;212213	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden;214215	fn get_for_core(&self, key: IStr, sup_this: SupThis, omit_only: bool) -> Result<GetFor>;216	fn field_visibility_core(&self, field: IStr) -> FieldVisibility;217218	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()>;219}220221#[derive(Clone, Trace)]222pub struct WeakObjValue(#[trace(skip)] Weak<ObjValueInner>);223impl Debug for WeakObjValue {224	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {225		f.debug_tuple("WeakObjValue").finish()226	}227}228229impl PartialEq for WeakObjValue {230	fn eq(&self, other: &Self) -> bool {231		Weak::ptr_eq(&self.0, &other.0)232	}233}234235impl Eq for WeakObjValue {}236impl Hash for WeakObjValue {237	fn hash<H: Hasher>(&self, hasher: &mut H) {238		// Safety: usize is POD239		let addr = unsafe { *std::ptr::addr_of!(self.0).cast() };240		hasher.write_usize(addr);241	}242}243244cc_dyn!(245	#[derive(Clone, Debug)]246	CcObjectCore, ObjectCore,247	pub fn new() {...}248);249#[derive(Trace, Educe)]250#[educe(Debug)]251struct ObjValueInner {252	cores: Vec<CcObjectCore>,253	assertions_ran: Cell<bool>,254	value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,255}256257thread_local! {258	static RUNNING_ASSERTIONS: RefCell<FxHashSet<ObjValue>> = RefCell::default();259}260fn is_asserting(obj: &ObjValue) -> bool {261	RUNNING_ASSERTIONS.with_borrow(|v| v.contains(obj))262}263/// Returns false if already asserting264fn start_asserting(obj: &ObjValue) -> bool {265	RUNNING_ASSERTIONS.with_borrow_mut(|v| v.insert(obj.clone()))266}267fn finish_asserting(obj: &ObjValue) {268	RUNNING_ASSERTIONS.with_borrow_mut(|v| {269		let r = v.remove(obj);270		debug_assert!(271			r,272			"finish_asserting was called before start_asserting or twice"273		);274	});275}276277thread_local! {278	static EMPTY_OBJ: ObjValue = ObjValue(Cc::new(ObjValueInner {279		cores: vec![],280		assertions_ran: Cell::new(true),281		value_cache: Default::default(),282	}))283}284285#[allow(clippy::module_name_repetitions)]286#[derive(Clone, Trace, Debug, Educe)]287#[educe(PartialEq, Hash, Eq)]288pub struct ObjValue(289	#[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc<ObjValueInner>,290);291292impl ObjValue {293	pub fn empty() -> Self {294		EMPTY_OBJ.with(|v| v.clone())295	}296	pub fn is_empty(&self) -> bool {297		self.0.cores.is_empty() || self.len() == 0298	}299}300301#[derive(Trace, Debug)]302struct StandaloneSuperCore {303	sup: CoreIdx,304	this: ObjValue,305}306impl ObjectCore for StandaloneSuperCore {307	fn enum_fields_core(308		&self,309		super_depth: &mut SuperDepth,310		handler: &mut EnumFieldsHandler<'_>,311	) -> bool {312		self.this.enum_fields_idx(super_depth, handler, self.sup)313	}314315	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {316		if self.this.has_field_include_hidden_idx(name, self.sup) {317			HasFieldIncludeHidden::Exists318		} else {319			HasFieldIncludeHidden::NotFound320		}321	}322323	fn get_for_core(&self, key: IStr, _sup_this: SupThis, omit_only: bool) -> Result<GetFor> {324		if omit_only {325			return Ok(GetFor::NotFound);326		}327		let v = self.this.get_idx(key, self.sup)?;328		Ok(v.map_or(GetFor::NotFound, |v| GetFor::Final(v)))329	}330331	fn field_visibility_core(&self, field: IStr) -> FieldVisibility {332		match self.this.field_visibility_idx(field, self.sup) {333			Some(c) => FieldVisibility::Found(c),334			None => FieldVisibility::NotFound,335		}336	}337338	fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {339		self.this.run_assertions()340	}341}342343#[derive(Debug, Acyclic)]344struct OmitFieldsCore {345	omit: FxHashSet<IStr>,346	prev_layers: usize,347}348impl ObjectCore for OmitFieldsCore {349	fn enum_fields_core(350		&self,351		super_depth: &mut SuperDepth,352		handler: &mut EnumFieldsHandler<'_>,353	) -> bool {354		let mut fi = FieldIndex::default();355		for f in &self.omit {356			if let ControlFlow::Break(()) = handler(357				*super_depth,358				fi,359				f.clone(),360				EnumFields::Omit(Saturating(self.prev_layers)),361			) {362				return false;363			}364			fi = fi.next();365		}366		true367	}368369	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {370		if self.omit.contains(&name) {371			return HasFieldIncludeHidden::Omit(Saturating(self.prev_layers));372		}373		HasFieldIncludeHidden::NotFound374	}375376	fn get_for_core(&self, key: IStr, _sup_this: SupThis, _omit_only: bool) -> Result<GetFor> {377		if self.omit.contains(&key) {378			return Ok(GetFor::Omit(Saturating(self.prev_layers)));379		}380		Ok(GetFor::NotFound)381	}382383	fn field_visibility_core(&self, field: IStr) -> FieldVisibility {384		if self.omit.contains(&field) {385			return FieldVisibility::Omit(Saturating(self.prev_layers));386		}387		FieldVisibility::NotFound388	}389390	fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {391		Ok(())392	}393}394395#[derive(Hash, PartialEq, Eq, Trace, Clone, Copy, Debug)]396struct CoreIdx {397	idx: usize,398}399impl CoreIdx {400	fn super_exists(self) -> bool {401		self.idx != 0402	}403}404#[derive(Trace, Clone, PartialEq, Eq, Hash, Debug)]405pub struct SupThis {406	sup: CoreIdx,407	this: ObjValue,408}409impl SupThis {410	pub fn has_super(&self) -> bool {411		self.sup.super_exists()412	}413	/// Implementation of `"field" in super` operation,414	/// works faster than standalone super path.415	///416	/// In case of no `super` existence, returns false.417	pub fn field_in_super(&self, field: IStr) -> bool {418		self.this.has_field_include_hidden_idx(field, self.sup)419	}420	/// Implementation of `super.field` operation,421	/// works faster than standalone super path.422	///423	/// In case of no `super` existence, returns `NoSuperFound`424	pub fn get_super(&self, field: IStr) -> Result<Option<Val>> {425		if !self.sup.super_exists() {426			bail!(NoSuperFound);427		}428		self.this.get_idx(field, self.sup)429	}430	/// `super` with `self` overriden for top-level lookups.431	/// Exists when super appears outside of `super.field`/`"field" in super` expressions432	/// Exclusive to jrsonnet.433	///434	/// Might return `NoSuperFound` error.435	pub fn standalone_super(&self) -> Result<ObjValue> {436		if !self.sup.super_exists() {437			bail!(NoSuperFound)438		}439		let mut out = ObjValue::builder();440		out.reserve_cores(1).extend_with_core(StandaloneSuperCore {441			sup: self.sup,442			this: self.this.clone(),443		});444		Ok(out.build())445	}446	pub fn this(&self) -> &ObjValue {447		&self.this448	}449	pub fn downgrade(self) -> WeakSupThis {450		WeakSupThis {451			sup: self.sup,452			this: self.this.downgrade(),453		}454	}455}456#[derive(Trace, PartialEq, Eq, Hash, Debug)]457pub struct WeakSupThis {458	sup: CoreIdx,459	this: WeakObjValue,460}461462impl ObjValue {463	pub fn builder() -> ObjValueBuilder {464		ObjValueBuilder::new()465	}466	pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {467		ObjValueBuilder::with_capacity(capacity)468	}469	pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {470		let mut out = ObjValueBuilder::with_capacity(1);471		out.with_super(self);472		let mut member = out.field(key);473		if value.flags.add() {474			member = member.add();475		}476		if let Some(loc) = value.location {477			member = member.with_location(loc);478		}479		let _ = member480			.with_visibility(value.flags.visibility())481			.binding(value.invoke);482		out.build()483	}484	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {485		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())486	}487488	pub fn extend(&mut self) -> ObjValueBuilder {489		let mut out = ObjValueBuilder::new();490		out.with_super(self.clone());491		out492	}493494	#[must_use]495	pub fn extend_from(&self, sup: Self) -> Self {496		let mut cores = sup.0.cores.clone();497		cores.extend(self.0.cores.iter().cloned());498		ObjValue(Cc::new(ObjValueInner {499			cores,500			value_cache: RefCell::default(),501			assertions_ran: Cell::new(false),502		}))503	}504	// #[must_use]505	// pub fn with_this(&self, this: Self) -> Self {506	// 	self.0.with_this(self.clone(), this)507	// }508	/// Returns amount of visible object fields509	/// If object only contains hidden fields - may return zero.510	pub fn len(&self) -> usize {511		self.fields_visibility()512			.values()513			.filter(|d| d.visible())514			.count()515	}516	/// For each field, calls callback.517	/// If callback returns false - ends iteration prematurely.518	///519	/// Returns false if ended prematurely520	pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool {521		let mut super_depth = SuperDepth::default();522		self.enum_fields_idx(523			&mut super_depth,524			handler,525			CoreIdx {526				idx: self.0.cores.len(),527			},528		)529	}530	fn enum_fields_idx(531		&self,532		super_depth: &mut SuperDepth,533		handler: &mut EnumFieldsHandler<'_>,534		idx: CoreIdx,535	) -> bool {536		for core in self.0.cores[..idx.idx].iter().rev() {537			if !core.0.enum_fields_core(super_depth, handler) {538				return false;539			}540			super_depth.deepen();541		}542		true543	}544545	pub fn has_field_include_hidden(&self, name: IStr) -> bool {546		self.has_field_include_hidden_idx(547			name,548			CoreIdx {549				idx: self.0.cores.len(),550			},551		)552	}553	fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {554		let mut skip = Saturating(0usize);555		for ele in self.0.cores[..core.idx].iter().rev() {556			match ele.0.has_field_include_hidden_core(name.clone()) {557				HasFieldIncludeHidden::Exists => {558					if skip.0 == 0 {559						return true;560					}561				}562				HasFieldIncludeHidden::Omit(new_skip) => {563					// +1 including this core564					skip = skip.max(new_skip + Saturating(1));565				}566				HasFieldIncludeHidden::NotFound => {}567			}568			skip -= 1;569		}570		false571	}572	pub fn has_field(&self, name: IStr) -> bool {573		match self.field_visibility(name) {574			Some(Visibility::Unhide | Visibility::Normal) => true,575			Some(Visibility::Hidden) | None => false,576		}577	}578	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {579		if include_hidden {580			self.has_field_include_hidden(name)581		} else {582			self.has_field(name)583		}584	}585	pub fn get(&self, key: IStr) -> Result<Option<Val>> {586		self.get_idx(587			key,588			CoreIdx {589				idx: self.0.cores.len(),590			},591		)592	}593594	fn get_idx(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {595		let cache_key = (key.clone(), core);596		{597			let mut cache = self.0.value_cache.borrow_mut();598			// entry_ref candidate?599			match cache.entry(cache_key.clone()) {600				Entry::Occupied(v) => match v.get() {601					CacheValue::Cached(v) => return v.clone(),602					CacheValue::Pending => {603						if !is_asserting(self) {604							bail!(InfiniteRecursionDetected);605						}606					}607				},608				Entry::Vacant(v) => {609					v.insert(CacheValue::Pending);610				}611			};612		}613		let result = self.get_idx_uncached(key, core);614		{615			let mut cache = self.0.value_cache.borrow_mut();616			cache.insert(cache_key, CacheValue::Cached(result.clone()));617		}618		result619	}620	fn get_idx_uncached(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {621		self.run_assertions()?;622		let mut add_stack = Vec::with_capacity(2);623		let mut skip = Saturating(0);624		for (sup, core) in self.0.cores[..core.idx].iter().enumerate().rev() {625			let sup_this = SupThis {626				sup: CoreIdx { idx: sup },627				this: self.clone(),628			};629			match core.0.get_for_core(key.clone(), sup_this, skip.0 != 0)? {630				GetFor::Final(val) if add_stack.is_empty() => {631					if skip.0 == 0 {632						return Ok(Some(val));633					}634				}635				GetFor::Final(val) => {636					if skip.0 == 0 {637						add_stack.push(val);638						break;639					}640				}641				GetFor::SuperPlus(val) => {642					if skip.0 == 0 {643						add_stack.push(val);644					}645				}646				GetFor::Omit(new_skip) => {647					// +1 including this core648					skip = skip.max(new_skip + Saturating(1));649				}650				GetFor::NotFound => {}651			}652			skip -= 1;653		}654		if add_stack.is_empty() {655			// None of layers had this field656			return Ok(None);657		} else if add_stack.len() == 1 {658			// A layer had this field, but it wanted this field to be added with super.659			// However, no super had this field, fail-safe660			return Ok(Some(add_stack.pop().expect("single element on stack")));661		}662		let mut values = add_stack.into_iter().rev();663		let init = values.next().expect("at least 2 elements");664665		values666			.try_fold(init, |a, b| evaluate_add_op(&a, &b))667			.map(Some)668669		// self.0.get_raw(key, this)670	}671672	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {673		let Some(value) = self.get(key.clone())? else {674			let suggestions = suggest_object_fields(self, key.clone());675			bail!(NoSuchField(key, suggestions))676		};677		Ok(value)678	}679680	fn field_visibility(&self, field: IStr) -> Option<Visibility> {681		self.field_visibility_idx(682			field,683			CoreIdx {684				idx: self.0.cores.len(),685			},686		)687	}688	fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {689		let mut exists = false;690		let mut skip = Saturating(0usize);691		for ele in self.0.cores[..core.idx].iter().rev() {692			let vis = ele.0.field_visibility_core(field.clone());693			match vis {694				FieldVisibility::Found(vis @ (Visibility::Unhide | Visibility::Hidden)) => {695					if skip.0 == 0 {696						return Some(vis);697					}698				}699				FieldVisibility::Found(Visibility::Normal) => {700					if skip.0 == 0 {701						exists = true702					}703				}704				FieldVisibility::NotFound => {}705				FieldVisibility::Omit(new_skip) => {706					// +1 including this core707					skip = skip.max(new_skip + Saturating(1));708				}709			}710			skip -= 1;711		}712		exists.then_some(Visibility::Normal)713	}714715	pub fn run_assertions(&self) -> Result<()> {716		if self.0.assertions_ran.get() {717			return Ok(());718		}719		if !start_asserting(self) {720			return Ok(());721		}722		for (idx, ele) in self.0.cores.iter().enumerate() {723			let sup_this = SupThis {724				sup: CoreIdx { idx },725				this: self.clone(),726			};727			ele.0.run_assertions_core(sup_this).inspect_err(|_e| {728				finish_asserting(self);729			})?;730		}731		finish_asserting(self);732		self.0.assertions_ran.set(true);733		Ok(())734	}735736	pub fn iter(737		&self,738		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,739	) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {740		let fields = self.fields(741			#[cfg(feature = "exp-preserve-order")]742			preserve_order,743		);744		fields.into_iter().map(|field| {745			(746				field.clone(),747				self.get(field)748					.map(|opt| opt.expect("iterating over keys, field exists")),749			)750		})751	}752	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {753		if !self.has_field_ex(key.clone(), true) {754			return None;755		}756		#[derive(Trace)]757		struct ObjFieldThunk {758			obj: ObjValue,759			key: IStr,760		}761		impl ThunkValue for ObjFieldThunk {762			type Output = Val;763764			fn get(&self) -> Result<Self::Output> {765				self.obj766					.get(self.key.clone())767					.transpose()768					.expect("field existence checked")769			}770		}771772		Some(Thunk::new(ObjFieldThunk {773			obj: self.clone(),774			key,775		}))776	}777	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {778		#[derive(Trace)]779		struct ObjFieldThunk {780			obj: ObjValue,781			key: IStr,782		}783		impl ThunkValue for ObjFieldThunk {784			type Output = Val;785786			fn get(&self) -> Result<Self::Output> {787				self.obj.get_or_bail(self.key.clone())788			}789		}790791		Thunk::new(ObjFieldThunk {792			obj: self.clone(),793			key,794		})795	}796	pub fn ptr_eq(a: &Self, b: &Self) -> bool {797		Cc::ptr_eq(&a.0, &b.0)798	}799	pub fn downgrade(self) -> WeakObjValue {800		WeakObjValue(self.0.downgrade())801	}802}803804#[derive(Debug)]805struct FieldVisibilityData {806	omitted_until: Saturating<usize>,807	exists_visible: Option<Visibility>,808	#[cfg(feature = "exp-preserve-order")]809	key: FieldSortKey,810}811impl FieldVisibilityData {812	fn visible(&self) -> bool {813		self.exists_visible814			.expect("non-existing fields shall be dropped at the end of fn fields_visibility()")815			.is_visible()816	}817	#[cfg(feature = "exp-preserve-order")]818	fn sort_key(&self) -> FieldSortKey {819		self.key820	}821}822823impl ObjValue {824	fn fields_visibility(&self) -> FxHashMap<IStr, FieldVisibilityData> {825		let mut out = FxHashMap::default();826827		let mut super_depth = SuperDepth::default();828		let mut omit_index = Saturating(0);829		for core in self.0.cores.iter().rev() {830			core.0831				.enum_fields_core(&mut super_depth, &mut |_depth, _index, name, visibility| {832					let entry = out.entry(name);833					let data = entry.or_insert(FieldVisibilityData {834						exists_visible: None,835						#[cfg(feature = "exp-preserve-order")]836						key: FieldSortKey::new(_depth, _index),837						omitted_until: omit_index,838					});839					match visibility {840						EnumFields::Omit(new_skip) => {841							// +1 including this core842							data.omitted_until = data843								.omitted_until844								.max(omit_index + new_skip + Saturating(1));845						}846						EnumFields::Normal(Visibility::Normal) => {847							if data.omitted_until <= omit_index {848								if data.exists_visible.is_none() {849									data.exists_visible = Some(Visibility::Normal);850								}851							}852						}853						EnumFields::Normal(Visibility::Hidden) => {854							if data.omitted_until <= omit_index {855								data.exists_visible = Some(match data.exists_visible {856									// We're iterating in reverse, later unhide is preserved857									Some(Visibility::Unhide) => Visibility::Unhide,858									_ => Visibility::Hidden,859								});860							}861						}862						EnumFields::Normal(Visibility::Unhide) => {863							if data.omitted_until <= omit_index {864								data.exists_visible = Some(match data.exists_visible {865									// We're iterating in reverse, later hide is preserved866									Some(Visibility::Hidden) => Visibility::Hidden,867									_ => Visibility::Unhide,868								});869							}870						}871					};872					return ControlFlow::Continue(());873				});874875			super_depth.deepen();876			omit_index += 1;877		}878879		out.retain(|_, v| v.exists_visible.is_some());880881		out882	}883	pub fn fields_ex(884		&self,885		include_hidden: bool,886		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,887	) -> Vec<IStr> {888		#[cfg(feature = "exp-preserve-order")]889		if preserve_order {890			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self891				.fields_visibility()892				.into_iter()893				.filter(|(_, d)| include_hidden || d.visible())894				.enumerate()895				.map(|(idx, (k, d))| (k, (d.sort_key(), idx)))896				.unzip();897			keys.sort_unstable_by_key(|v| v.0);898			// Reorder in-place by resulting indexes899			for i in 0..fields.len() {900				let x = fields[i].clone();901				let mut j = i;902				loop {903					let k = keys[j].1;904					keys[j].1 = j;905					if k == i {906						break;907					}908					fields[j] = fields[k].clone();909					j = k;910				}911				fields[j] = x;912			}913			return fields;914		}915916		let mut fields: Vec<_> = self917			.fields_visibility()918			.into_iter()919			.filter(|(_, d)| include_hidden || d.visible())920			.map(|(k, _)| k)921			.collect();922		fields.sort_unstable();923		fields924	}925	pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec<IStr> {926		self.fields_ex(927			false,928			#[cfg(feature = "exp-preserve-order")]929			preserve_order,930		)931	}932	pub fn values_ex(933		&self,934		include_hidden: bool,935		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,936	) -> ArrValue {937		ArrValue::new(PickObjectValues::new(938			self.clone(),939			self.fields_ex(940				include_hidden,941				#[cfg(feature = "exp-preserve-order")]942				preserve_order,943			),944		))945	}946	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {947		self.values_ex(948			false,949			#[cfg(feature = "exp-preserve-order")]950			preserve_order,951		)952	}953	pub fn key_values_ex(954		&self,955		include_hidden: bool,956		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,957	) -> ArrValue {958		ArrValue::new(PickObjectKeyValues::new(959			self.clone(),960			self.fields_ex(961				include_hidden,962				#[cfg(feature = "exp-preserve-order")]963				preserve_order,964			),965		))966	}967	pub fn key_values(968		&self,969		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,970	) -> ArrValue {971		self.key_values_ex(972			false,973			#[cfg(feature = "exp-preserve-order")]974			preserve_order,975		)976	}977}978979impl OopObject {980	pub fn new(981		this_entries: FxHashMap<IStr, ObjMember>,982		assertions: Vec<CcObjectAssertion>,983	) -> Self {984		Self {985			this_entries,986			assertions,987		}988	}989}990991impl ObjectCore for OopObject {992	fn enum_fields_core(993		&self,994		super_depth: &mut SuperDepth,995		handler: &mut EnumFieldsHandler<'_>,996	) -> bool {997		for (name, member) in self.this_entries.iter() {998			if matches!(999				handler(1000					*super_depth,1001					member.original_index,1002					name.clone(),1003					EnumFields::Normal(member.flags.visibility()),1004				),1005				ControlFlow::Break(())1006			) {1007				return false;1008			}1009		}1010		true1011	}10121013	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {1014		if self.this_entries.contains_key(&name) {1015			HasFieldIncludeHidden::Exists1016		} else {1017			HasFieldIncludeHidden::NotFound1018		}1019	}10201021	fn get_for_core(&self, key: IStr, sup_this: SupThis, omit_only: bool) -> Result<GetFor> {1022		if omit_only {1023			return Ok(GetFor::NotFound);1024		}1025		match self.this_entries.get(&key) {1026			Some(k) => {1027				let v = k.invoke.evaluate(sup_this)?;1028				Ok(if k.flags.add() {1029					GetFor::SuperPlus(v)1030				} else {1031					GetFor::Final(v)1032				})1033			}1034			None => Ok(GetFor::NotFound),1035		}1036	}1037	fn field_visibility_core(&self, name: IStr) -> FieldVisibility {1038		match self.this_entries.get(&name) {1039			Some(f) => FieldVisibility::Found(f.flags.visibility()),1040			None => FieldVisibility::NotFound,1041		}1042	}10431044	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {1045		if self.assertions.is_empty() {1046			return Ok(());1047		}1048		for assertion in self.assertions.iter() {1049			assertion.0.run(sup_this.clone())?;1050		}1051		Ok(())1052	}1053}10541055#[allow(clippy::module_name_repetitions)]1056pub struct ObjValueBuilder {1057	sup: Vec<CcObjectCore>,10581059	new: OopObject,1060	next_field_index: FieldIndex,1061}1062impl ObjValueBuilder {1063	pub fn new() -> Self {1064		Self::with_capacity(0)1065	}1066	pub fn with_capacity(capacity: usize) -> Self {1067		Self {1068			sup: vec![],1069			new: OopObject {1070				assertions: vec![],1071				this_entries: FxHashMap::with_capacity(capacity),1072			},1073			next_field_index: FieldIndex::default(),1074		}1075	}1076	pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {1077		self.sup.reserve_exact(capacity);1078		self1079	}1080	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {1081		self.new.assertions.reserve_exact(capacity);1082		self1083	}1084	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {1085		self.sup = super_obj.0.cores.clone();1086		self1087	}10881089	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {1090		self.new.assertions.push(CcObjectAssertion::new(assertion));1091		self1092	}1093	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {1094		let field_index = self.next_field_index;1095		self.next_field_index = self.next_field_index.next();1096		ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)1097	}1098	/// Preset for common method definiton pattern:1099	/// Create a hidden field with the function value.1100	///1101	/// `.field(name).hide().value(Val::function(value))`1102	pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {1103		self.field(name).hide().value(Val::Func(value.into()));1104		self1105	}1106	pub fn try_method(1107		&mut self,1108		name: impl Into<IStr>,1109		value: impl Into<FuncVal>,1110	) -> Result<&mut Self> {1111		self.field(name).hide().try_value(Val::Func(value.into()))?;1112		Ok(self)1113	}11141115	pub fn extend_with_core(&mut self, core: impl ObjectCore) {1116		self.commit();1117		self.sup.push(CcObjectCore::new(core));1118	}11191120	fn commit(&mut self) {1121		if !self.new.is_empty() {1122			self.sup.push(CcObjectCore::new(mem::take(&mut self.new)));1123		}1124		self.next_field_index = FieldIndex::default();1125	}11261127	pub fn with_fields_omitted(&mut self, omit: FxHashSet<IStr>) {1128		self.commit();1129		self.sup.push(CcObjectCore::new(OmitFieldsCore {1130			omit,1131			prev_layers: self.sup.len(),1132		}));1133	}11341135	pub fn build(mut self) -> ObjValue {1136		self.commit();1137		if self.sup.is_empty() {1138			return ObjValue::empty();1139		}1140		ObjValue(Cc::new(ObjValueInner {1141			cores: self.sup,1142			assertions_ran: Cell::new(false),1143			value_cache: Default::default(),1144		}))1145	}1146}1147impl Default for ObjValueBuilder {1148	fn default() -> Self {1149		Self::with_capacity(0)1150	}1151}11521153#[allow(clippy::module_name_repetitions)]1154#[must_use = "value not added unless binding() was called"]1155pub struct ObjMemberBuilder<Kind> {1156	kind: Kind,1157	name: IStr,1158	add: bool,1159	visibility: Visibility,1160	original_index: FieldIndex,1161	location: Option<Span>,1162}11631164#[allow(clippy::missing_const_for_fn)]1165impl<Kind> ObjMemberBuilder<Kind> {1166	pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self {1167		Self {1168			kind,1169			name,1170			original_index,1171			add: false,1172			visibility: Visibility::Normal,1173			location: None,1174		}1175	}11761177	pub const fn with_add(mut self, add: bool) -> Self {1178		self.add = add;1179		self1180	}1181	pub fn add(self) -> Self {1182		self.with_add(true)1183	}1184	pub fn with_visibility(mut self, visibility: Visibility) -> Self {1185		self.visibility = visibility;1186		self1187	}1188	pub fn hide(self) -> Self {1189		self.with_visibility(Visibility::Hidden)1190	}1191	pub fn with_location(mut self, location: Span) -> Self {1192		self.location = Some(location);1193		self1194	}1195	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {1196		(1197			self.kind,1198			self.name,1199			ObjMember {1200				flags: ObjFieldFlags::new(self.add, self.visibility),1201				original_index: self.original_index,1202				invoke: binding,1203				location: self.location,1204			},1205		)1206	}1207}12081209pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);1210impl ObjMemberBuilder<ValueBuilder<'_>> {1211	/// Inserts value, replacing if it is already defined1212	pub fn value(self, value: impl Into<Val>) {1213		let (receiver, name, member) =1214			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1215		let entry = receiver.0.new.this_entries.entry(name);1216		entry.insert_entry(member);1217	}1218	/// Inserts thunk, replacing if it is already defined1219	pub fn thunk(self, value: impl Into<Thunk<Val>>) {1220		let (receiver, name, member) = self.build_member(MaybeUnbound::Bound(value.into()));1221		let entry = receiver.0.new.this_entries.entry(name);1222		entry.insert_entry(member);1223	}12241225	/// Tries to insert value, returns an error if it was already defined1226	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {1227		self.try_thunk(Thunk::evaluated(value.into()))1228	}1229	pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {1230		self.binding(MaybeUnbound::Bound(value.into()))1231	}1232	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {1233		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)))1234	}1235	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {1236		let (receiver, name, member) = self.build_member(binding);1237		let location = member.location.clone();1238		let old = receiver.0.new.this_entries.insert(name.clone(), member);1239		if old.is_some() {1240			in_frame(1241				CallLocation(location.as_ref()),1242				|| format!("field <{}> initializtion", name.clone()),1243				|| bail!(DuplicateFieldName(name.clone())),1244			)?;1245		}1246		Ok(())1247	}1248}12491250pub struct ExtendBuilder<'v>(&'v mut ObjValue);1251impl ObjMemberBuilder<ExtendBuilder<'_>> {1252	pub fn value(self, value: impl Into<Val>) {1253		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1254	}1255	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) {1256		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)));1257	}1258	pub fn binding(self, binding: MaybeUnbound) {1259		let (receiver, name, member) = self.build_member(binding);1260		let new = receiver.0.clone();1261		*receiver.0 = new.extend_with_raw_member(name, member);1262	}1263}
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,13 +2,14 @@
 	cell::RefCell,
 	cmp::Ordering,
 	fmt::{self, Debug, Display},
+	marker::PhantomData,
 	mem::replace,
 	num::NonZeroU32,
 	ops::Deref,
 	rc::Rc,
 };
 
-use jrsonnet_gcmodule::{Acyclic, Cc, Trace, TraceBox};
+use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace};
 use jrsonnet_interner::IStr;
 pub use jrsonnet_macros::Thunk;
 use jrsonnet_types::ValType;
@@ -28,56 +29,106 @@
 
 pub trait ThunkValue: Trace {
 	type Output;
-	fn get(self: Box<Self>) -> Result<Self::Output>;
+	fn get(&self) -> Result<Self::Output>;
 }
 
 #[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>,
+enum MemoizedClusureThunkInner<D: Trace, T: Trace> {
+	Computed(T),
+	Errored(Error),
+	Waiting {
+		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<T>,
+	},
+	Pending,
 }
-impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {
-	pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {
-		Self { env, closure }
+#[derive(Trace)]
+pub struct MemoizedClosureThunk<D: Trace, T: Trace>(RefCell<MemoizedClusureThunkInner<D, T>>);
+impl<D: Trace, T: Trace> MemoizedClosureThunk<D, T> {
+	pub fn new(env: D, closure: fn(D) -> Result<T>) -> Self {
+		Self(RefCell::new(MemoizedClusureThunkInner::Waiting {
+			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)
-	}
-}
+impl<D: Trace, T: Trace + Clone> ThunkValue for MemoizedClosureThunk<D, T> {
+	type Output = T;
 
-#[derive(Trace)]
-enum ThunkInner<T: Trace> {
-	Computed(T),
-	Errored(Error),
-	Waiting(TraceBox<dyn ThunkValue<Output = T>>),
-	Pending,
+	fn get(&self) -> Result<Self::Output> {
+		match &*self.0.borrow() {
+			MemoizedClusureThunkInner::Computed(v) => return Ok(v.clone()),
+			MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),
+			MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
+			MemoizedClusureThunkInner::Waiting { .. } => (),
+		};
+		let MemoizedClusureThunkInner::Waiting { env, closure } = replace(
+			&mut *self.0.borrow_mut(),
+			MemoizedClusureThunkInner::Pending,
+		) else {
+			unreachable!();
+		};
+		let new_value = match closure(env) {
+			Ok(v) => v,
+			Err(e) => {
+				*self.0.borrow_mut() = MemoizedClusureThunkInner::Errored(e.clone());
+				return Err(e);
+			}
+		};
+		*self.0.borrow_mut() = MemoizedClusureThunkInner::Computed(new_value.clone());
+		Ok(new_value)
+	}
 }
 
-/// Lazily evaluated value
-#[allow(clippy::module_name_repetitions)]
-#[derive(Clone, Trace)]
-pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);
+cc_dyn!(
+	/// Lazily evaluated value
+	#[derive(Clone)] Thunk<V: Trace>,
+	ThunkValue<Output = V>,
+	pub fn new() {...}
+);
 
 impl<T: Trace> Thunk<T> {
-	pub fn evaluated(val: T) -> Self {
-		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))
-	}
-	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {
-		Self(Cc::new(RefCell::new(ThunkInner::Waiting(TraceBox(
-			Box::new(f),
-		)))))
+	pub fn evaluated(val: T) -> Self
+	where
+		T: Clone,
+	{
+		#[derive(Trace)]
+		struct EvaluatedThunk<T: Trace>(T);
+		impl<T> ThunkValue for EvaluatedThunk<T>
+		where
+			T: Clone + Trace,
+		{
+			type Output = T;
+
+			fn get(&self) -> Result<Self::Output> {
+				Ok(self.0.clone())
+			}
+		}
+		Self::new(EvaluatedThunk(val))
 	}
 	pub fn errored(e: Error) -> Self {
-		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))
+		#[derive(Trace)]
+		struct ErroredThunk<T: Trace>(Error, PhantomData<T>);
+		impl<T> ThunkValue for ErroredThunk<T>
+		where
+			T: Trace,
+		{
+			type Output = T;
+
+			fn get(&self) -> Result<Self::Output> {
+				Err(self.0.clone())
+			}
+		}
+		Self::new(ErroredThunk(e, PhantomData))
 	}
-	pub fn result(res: Result<T, Error>) -> Self {
+	pub fn result(res: Result<T, Error>) -> Self
+	where
+		T: Clone,
+	{
 		match res {
 			Ok(o) => Self::evaluated(o),
 			Err(e) => Self::errored(e),
@@ -101,25 +152,7 @@
 	/// - Lazy value evaluation returned error
 	/// - This method was called during inner value evaluation
 	pub fn evaluate(&self) -> Result<T> {
-		match &*self.0.borrow() {
-			ThunkInner::Computed(v) => return Ok(v.clone()),
-			ThunkInner::Errored(e) => return Err(e.clone()),
-			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
-			ThunkInner::Waiting(..) => (),
-		};
-		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)
-		else {
-			unreachable!();
-		};
-		let new_value = match value.0.get() {
-			Ok(v) => v,
-			Err(e) => {
-				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());
-				return Err(e);
-			}
-		};
-		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());
-		Ok(new_value)
+		self.0.get()
 	}
 }
 
@@ -134,7 +167,7 @@
 	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>
 	where
 		M: ThunkMapper<Input>,
-		M::Output: Trace,
+		M::Output: Trace + Clone,
 	{
 		let inner = self;
 		Thunk!(move || {
@@ -145,7 +178,7 @@
 	}
 }
 
-impl<T: Trace> From<Result<T>> for Thunk<T> {
+impl<T: Trace + Clone> From<Result<T>> for Thunk<T> {
 	fn from(value: Result<T>) -> Self {
 		match value {
 			Ok(o) => Self::evaluated(o),
@@ -162,7 +195,7 @@
 	}
 }
 
-impl<T: Trace + Default> Default for Thunk<T> {
+impl<T: Trace + Default + Clone> Default for Thunk<T> {
 	fn default() -> Self {
 		Self::evaluated(T::default())
 	}
modifiedcrates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth
--- a/crates/jrsonnet-formatter/src/comments.rs
+++ b/crates/jrsonnet-formatter/src/comments.rs
@@ -73,7 +73,10 @@
 						p!(out, str(" "));
 					}
 					p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */"));
-					if matches!(loc, CommentLocation::AboveItem | CommentLocation::EndOfItems) {
+					if matches!(
+						loc,
+						CommentLocation::AboveItem | CommentLocation::EndOfItems
+					) {
 						p!(out, nl);
 					}
 				} else if !lines.is_empty() {
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -879,6 +879,6 @@
 	quote! {{
 		#move_check
 		#(#trace_check)*
-		::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))
+		::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::MemoizedClosureThunk::new(#env, #closure))
 	}}.into()
 }
deletedresultdiffbeforeafterboth
--- a/result
+++ /dev/null
@@ -1 +0,0 @@
-/nix/store/nd6v7jksg1dqhpx4x4vqgy5ry1nkb9lk-jrsonnet-current
\ No newline at end of file
modifiedxtask/src/sourcegen/mod.rsdiffbeforeafterboth
--- a/xtask/src/sourcegen/mod.rs
+++ b/xtask/src/sourcegen/mod.rs
@@ -203,49 +203,57 @@
 			});
 
 			let mut type_positions: HashMap<String, usize> = HashMap::new();
-			let field_positions: Vec<_> = node.fields.iter().map(|field| {
-				let ty_str = field.ty().to_string();
-				let pos = *type_positions.get(&ty_str).unwrap_or(&0);
-				type_positions.insert(ty_str, pos + 1);
-				pos
-			}).collect();
+			let field_positions: Vec<_> = node
+				.fields
+				.iter()
+				.map(|field| {
+					let ty_str = field.ty().to_string();
+					let pos = *type_positions.get(&ty_str).unwrap_or(&0);
+					type_positions.insert(ty_str, pos + 1);
+					pos
+				})
+				.collect();
 
-			let methods = node.fields.iter().zip(field_positions.iter()).map(|(field, &pos)| {
-				let method_name = field.method_name(kinds);
-				let ty = field.ty();
+			let methods = node
+				.fields
+				.iter()
+				.zip(field_positions.iter())
+				.map(|(field, &pos)| {
+					let method_name = field.method_name(kinds);
+					let ty = field.ty();
 
-				if field.is_many() {
-					quote! {
-						pub fn #method_name(&self) -> AstChildren<#ty> {
-							support::children(&self.syntax)
+					if field.is_many() {
+						quote! {
+							pub fn #method_name(&self) -> AstChildren<#ty> {
+								support::children(&self.syntax)
+							}
 						}
-					}
-				} else if let Some(token_kind) = field.token_kind(kinds) {
-					quote! {
-						pub fn #method_name(&self) -> Option<#ty> {
-							support::token(&self.syntax, #token_kind)
+					} else if let Some(token_kind) = field.token_kind(kinds) {
+						quote! {
+							pub fn #method_name(&self) -> Option<#ty> {
+								support::token(&self.syntax, #token_kind)
+							}
 						}
-					}
-				} else if field.is_token_enum(grammar) {
-					quote! {
-						pub fn #method_name(&self) -> Option<#ty> {
-							support::token_child(&self.syntax)
+					} else if field.is_token_enum(grammar) {
+						quote! {
+							pub fn #method_name(&self) -> Option<#ty> {
+								support::token_child(&self.syntax)
+							}
 						}
-					}
-				} else if pos == 0 {
-					quote! {
-						pub fn #method_name(&self) -> Option<#ty> {
-							support::children(&self.syntax).next()
+					} else if pos == 0 {
+						quote! {
+							pub fn #method_name(&self) -> Option<#ty> {
+								support::children(&self.syntax).next()
+							}
 						}
-					}
-				} else {
-					quote! {
-						pub fn #method_name(&self) -> Option<#ty> {
-							support::children(&self.syntax).nth(#pos)
+					} else {
+						quote! {
+							pub fn #method_name(&self) -> Option<#ty> {
+								support::children(&self.syntax).nth(#pos)
+							}
 						}
 					}
-				}
-			});
+				});
 			(
 				quote! {
 					#[pretty_doc_comment_placeholder_workaround]