git.delta.rocks / jrsonnet / refs/commits / 3c9d13d48c21

difftreelog

feat OOP-aware objectRemoveKey

zkxotrvkYaroslav Bolyukin2026-02-08parent: #5ac33cf.patch.diff
in: master

17 files changed

modifiedbindings/jsonnet/src/val_make.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_make.rs
+++ b/bindings/jsonnet/src/val_make.rs
@@ -56,5 +56,5 @@
 /// Make a `JsonnetJsonValue` representing an object.
 #[no_mangle]
 pub extern "C" fn jsonnet_json_make_object(_vm: &VM) -> *mut Val {
-	Box::into_raw(Box::new(Val::Obj(ObjValue::new_empty())))
+	Box::into_raw(Box::new(Val::Obj(ObjValue::empty())))
 }
modifiedcrates/jrsonnet-cli/src/tla.rsdiffbeforeafterboth
--- a/crates/jrsonnet-cli/src/tla.rs
+++ b/crates/jrsonnet-cli/src/tla.rs
@@ -1,5 +1,7 @@
 use clap::Parser;
-use jrsonnet_evaluator::{IStr, error::Result, function::TlaArg, gc::WithCapacityExt as _, rustc_hash::FxHashMap};
+use jrsonnet_evaluator::{
+	error::Result, function::TlaArg, gc::WithCapacityExt as _, rustc_hash::FxHashMap, IStr,
+};
 
 use crate::{ExtFile, ExtStr};
 
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -1,5 +1,4 @@
-use std::ptr::addr_of;
-use std::{cell::OnceCell, hash::Hasher};
+use std::{cell::OnceCell, hash::Hasher, ptr::addr_of};
 
 use educe::Educe;
 use jrsonnet_gcmodule::{Cc, Trace};
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -11,7 +11,18 @@
 
 use self::destructure::destruct;
 use crate::{
-	Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, SupThis, Unbound, Val, arr::ArrValue, bail, destructure::evaluate_dest, error::{ErrorKind::*, suggest_object_fields}, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, function::{CallLocation, FuncDesc, FuncVal}, gc::WithCapacityExt as _, in_frame, typed::Typed, val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, with_state
+	arr::ArrValue,
+	bail,
+	destructure::evaluate_dest,
+	error::{suggest_object_fields, ErrorKind::*},
+	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
+	function::{CallLocation, FuncDesc, FuncVal},
+	gc::WithCapacityExt as _,
+	in_frame,
+	typed::Typed,
+	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
+	with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
+	ResultExt, SupThis, Unbound, Val,
 };
 pub mod destructure;
 pub mod operator;
@@ -137,10 +148,7 @@
 						)),
 					])));
 					destruct(var, value, fctx.clone(), &mut new_bindings)?;
-					let ctx = ctx
-						.clone()
-						.extend(new_bindings, None, None, None)
-						.into_future(fctx);
+					let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
 
 					evaluate_comp(ctx, &specs[1..], callback)?;
 				}
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -27,11 +27,11 @@
 use std::{
 	any::Any,
 	cell::{RefCell, RefMut},
+	clone::Clone,
 	collections::hash_map::Entry,
-	clone::Clone,
 	fmt::{self, Debug},
+	marker::PhantomData,
 	rc::Rc,
-	marker::PhantomData,
 };
 
 pub use ctx::*;
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};89use educe::Educe;10use jrsonnet_gcmodule::{cc_dyn, Cc, Trace, Weak};11use jrsonnet_interner::IStr;12use jrsonnet_parser::{Span, Visibility};13use rustc_hash::{FxHashMap, FxHashSet};1415use crate::{16	arr::{PickObjectKeyValues, PickObjectValues},17	bail,18	error::{suggest_object_fields, ErrorKind::*},19	function::{CallLocation, FuncVal},20	gc::WithCapacityExt as _,21	identity_hash, in_frame,22	operator::evaluate_add_op,23	val::ArrValue,24	CcUnbound, 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(super) fn deepen(self) {}48	}4950	#[derive(Clone, Copy, Debug)]51	pub struct FieldSortKey(());52	impl FieldSortKey {53		pub const fn new(_: SuperDepth, _: FieldIndex) -> Self {54			Self(())55		}56	}57}5859#[cfg(feature = "exp-preserve-order")]60mod ordering {61	use std::cmp::Reverse;6263	use jrsonnet_gcmodule::Trace;6465	#[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)]66	pub struct FieldIndex(u32);67	impl FieldIndex {68		pub fn next(self) -> Self {69			Self(self.0 + 1)70		}71	}7273	#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)]74	pub struct SuperDepth(u32);75	impl SuperDepth {76		pub(super) fn deepen(&mut self) {77			*self.0 += 178		}79	}8081	#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]82	pub struct FieldSortKey(Reverse<SuperDepth>, FieldIndex);83	impl FieldSortKey {84		pub fn new(depth: SuperDepth, index: FieldIndex) -> Self {85			Self(Reverse(depth), index)86		}87	}88}8990use ordering::{FieldIndex, FieldSortKey, SuperDepth};9192// 0 - add93//  12 - visibility94#[derive(Clone, Copy)]95pub struct ObjFieldFlags(u8);96impl ObjFieldFlags {97	fn new(add: bool, visibility: Visibility) -> Self {98		let mut v = 0;99		if add {100			v |= 1;101		}102		v |= match visibility {103			Visibility::Normal => 0b000,104			Visibility::Hidden => 0b010,105			Visibility::Unhide => 0b100,106		};107		Self(v)108	}109	pub fn add(&self) -> bool {110		self.0 & 1 != 0111	}112	pub fn visibility(&self) -> Visibility {113		match (self.0 & 0b110) >> 1 {114			0b00 => Visibility::Normal,115			0b01 => Visibility::Hidden,116			0b10 => Visibility::Unhide,117			_ => unreachable!(),118		}119	}120}121impl Debug for ObjFieldFlags {122	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {123		f.debug_struct("ObjFieldFlags")124			.field("add", &self.add())125			.field("visibility", &self.visibility())126			.finish()127	}128}129130#[allow(clippy::module_name_repetitions)]131#[derive(Debug, Trace)]132pub struct ObjMember {133	#[trace(skip)]134	flags: ObjFieldFlags,135	original_index: FieldIndex,136	pub invoke: MaybeUnbound,137	pub location: Option<Span>,138}139140cc_dyn!(CcObjectAssertion, ObjectAssertion);141pub trait ObjectAssertion: Trace {142	fn run(&self, sup_this: SupThis) -> Result<()>;143}144145// Field => This146147#[derive(Trace, Debug)]148enum CacheValue {149	Cached(Result<Option<Val>>),150	Pending,151}152153#[allow(clippy::module_name_repetitions)]154#[derive(Trace)]155#[trace(tracking(force))]156pub struct OopObject {157	// this: Option<ObjValue>,158	assertions: Cc<Vec<CcObjectAssertion>>,159	this_entries: Cc<FxHashMap<IStr, ObjMember>>,160	value_cache: RefCell<FxHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,161}162impl Debug for OopObject {163	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {164		f.debug_struct("OopObject")165			// .field("assertions", &self.assertions)166			// .field("assertions_ran", &self.assertions_ran)167			.field("this_entries", &self.this_entries)168			// .field("value_cache", &self.value_cache)169			.finish_non_exhaustive()170	}171}172173type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a;174175#[derive(Trace, Clone)]176pub enum ValueProcess {177	None,178	SuperPlus,179}180181pub trait ObjectCore: Trace + Any + Debug {182	// If callback returns false, iteration stops, and this call returns false.183	fn enum_fields_core(184		&self,185		super_depth: &mut SuperDepth,186		handler: &mut EnumFieldsHandler<'_>,187	) -> bool;188189	fn has_field_include_hidden(&self, name: IStr) -> bool;190191	fn get_for(&self, key: IStr, sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>>;192	// fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<(Val, ValueProcess)>>;193	fn field_visibility(&self, field: IStr) -> Option<Visibility>;194195	fn run_assertions_raw(&self, sup_this: SupThis) -> Result<()>;196}197198#[derive(Clone, Trace)]199pub struct WeakObjValue(#[trace(skip)] Weak<ObjValueInner>);200impl Debug for WeakObjValue {201	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {202		f.debug_tuple("WeakObjValue").finish()203	}204}205206impl PartialEq for WeakObjValue {207	fn eq(&self, other: &Self) -> bool {208		Weak::ptr_eq(&self.0, &other.0)209	}210}211212impl Eq for WeakObjValue {}213impl Hash for WeakObjValue {214	fn hash<H: Hasher>(&self, hasher: &mut H) {215		// Safety: usize is POD216		let addr = unsafe { *std::ptr::addr_of!(self.0).cast() };217		hasher.write_usize(addr);218	}219}220221cc_dyn!(222	#[derive(Clone, Debug)]223	ObjCore, ObjectCore,224	pub fn new() {...}225);226#[derive(Trace, Educe)]227#[educe(Debug)]228struct ObjValueInner {229	cores: Vec<ObjCore>,230	assertions_ran: Cell<bool>,231	value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,232}233234thread_local! {235	static RUNNING_ASSERTIONS: RefCell<FxHashSet<ObjValue>> = RefCell::default();236}237fn is_asserting(obj: &ObjValue) -> bool {238	RUNNING_ASSERTIONS.with_borrow(|v| v.contains(obj))239}240/// Returns false if already asserting241fn start_asserting(obj: &ObjValue) -> bool {242	RUNNING_ASSERTIONS.with_borrow_mut(|v| v.insert(obj.clone()))243}244fn finish_asserting(obj: &ObjValue) {245	RUNNING_ASSERTIONS.with_borrow_mut(|v| {246		let r = v.remove(obj);247		debug_assert!(248			r,249			"finish_asserting was called before start_asserting or twice"250		);251	});252}253254#[allow(clippy::module_name_repetitions)]255#[derive(Clone, Trace, Debug, Educe)]256#[educe(PartialEq, Hash, Eq)]257pub struct ObjValue(258	#[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc<ObjValueInner>,259);260261#[derive(Trace, Debug)]262struct StandaloneSuperCore {263	sup: CoreIdx,264	this: ObjValue,265}266impl ObjectCore for StandaloneSuperCore {267	fn enum_fields_core(268		&self,269		super_depth: &mut SuperDepth,270		handler: &mut EnumFieldsHandler<'_>,271	) -> bool {272		self.this273			.enum_fields_internal(super_depth, handler, self.sup)274	}275276	fn has_field_include_hidden(&self, name: IStr) -> bool {277		self.this.has_field_include_hidden_idx(name, self.sup)278	}279280	fn get_for(&self, key: IStr, _sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>> {281		let v = self.this.get_idx(key, self.sup)?;282		Ok(v.map(|v| (v, ValueProcess::None)))283	}284285	fn field_visibility(&self, field: IStr) -> Option<Visibility> {286		self.this.field_visibility_idx(field, self.sup)287	}288289	fn run_assertions_raw(&self, _sup_this: SupThis) -> Result<()> {290		self.this.run_assertions()291	}292}293294#[derive(Debug, Trace)]295struct EmptyObject;296impl ObjectCore for EmptyObject {297	fn enum_fields_core(298		&self,299		_super_depth: &mut SuperDepth,300		_handler: &mut EnumFieldsHandler<'_>,301	) -> bool {302		true303	}304305	fn has_field_include_hidden(&self, _name: IStr) -> bool {306		false307	}308309	fn get_for(&self, _key: IStr, _sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>> {310		Ok(None)311	}312313	fn run_assertions_raw(&self, _sup_this: SupThis) -> Result<()> {314		Ok(())315	}316317	fn field_visibility(&self, _field: IStr) -> Option<Visibility> {318		None319	}320}321322#[derive(Hash, PartialEq, Eq, Trace, Clone, Copy, Debug)]323struct CoreIdx {324	idx: usize,325}326impl CoreIdx {327	fn super_exists(self) -> bool {328		self.idx != 0329	}330}331#[derive(Trace, Clone, PartialEq, Eq, Hash, Debug)]332pub struct SupThis {333	sup: CoreIdx,334	this: ObjValue,335}336impl SupThis {337	pub fn has_super(&self) -> bool {338		self.sup.super_exists()339	}340	/// Implementation of `"field" in super` operation,341	/// works faster than standalone super path.342	///343	/// In case of no `super` existence, returns false.344	pub fn field_in_super(&self, field: IStr) -> bool {345		self.this.has_field_include_hidden_idx(field, self.sup)346	}347	/// Implementation of `super.field` operation,348	/// works faster than standalone super path.349	///350	/// In case of no `super` existence, returns `NoSuperFound`351	pub fn get_super(&self, field: IStr) -> Result<Option<Val>> {352		if !self.sup.super_exists() {353			bail!(NoSuperFound);354		}355		self.this.get_idx(field, self.sup)356	}357	/// `super` with `self` overriden for top-level lookups.358	/// Exists when super appears outside of `super.field`/`"field" in super` expressions359	/// Exclusive to jrsonnet.360	///361	/// Might return `NoSuperFound` error.362	pub fn standalone_super(&self) -> Result<ObjValue> {363		if !self.sup.super_exists() {364			bail!(NoSuperFound)365		}366		Ok(ObjValue::new(StandaloneSuperCore {367			sup: self.sup,368			this: self.this.clone(),369		}))370	}371	pub fn this(&self) -> &ObjValue {372		&self.this373	}374	pub fn downgrade(self) -> WeakSupThis {375		WeakSupThis {376			sup: self.sup,377			this: self.this.downgrade(),378		}379	}380}381#[derive(Trace, PartialEq, Eq, Hash, Debug)]382pub struct WeakSupThis {383	sup: CoreIdx,384	this: WeakObjValue,385}386387impl ObjValue {388	pub fn new(v: impl ObjectCore) -> Self {389		Self(Cc::new(ObjValueInner {390			cores: vec![ObjCore::new(v)],391			assertions_ran: Cell::new(false),392			value_cache: RefCell::new(FxHashMap::new()),393		}))394	}395	pub fn new_empty() -> Self {396		Self::new(EmptyObject)397	}398	pub fn builder() -> ObjValueBuilder {399		ObjValueBuilder::new()400	}401	pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {402		ObjValueBuilder::with_capacity(capacity)403	}404	pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {405		let mut out = ObjValueBuilder::with_capacity(1);406		out.with_super(self);407		let mut member = out.field(key);408		if value.flags.add() {409			member = member.add();410		}411		if let Some(loc) = value.location {412			member = member.with_location(loc);413		}414		let _ = member415			.with_visibility(value.flags.visibility())416			.binding(value.invoke);417		out.build()418	}419	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {420		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())421	}422423	#[must_use]424	pub fn extend_from(&self, sup: Self) -> Self {425		let mut cores = sup.0.cores.clone();426		cores.extend(self.0.cores.iter().cloned());427		ObjValue(Cc::new(ObjValueInner {428			cores,429			value_cache: RefCell::default(),430			assertions_ran: Cell::new(false),431		}))432	}433	// #[must_use]434	// pub fn with_this(&self, this: Self) -> Self {435	// 	self.0.with_this(self.clone(), this)436	// }437	/// Returns amount of visible object fields438	/// If object only contains hidden fields - may return zero.439	pub fn len(&self) -> usize {440		self.fields_visibility()441			.iter()442			.filter(|(_, (visible, _))| *visible)443			.count()444	}445	pub fn is_empty(&self) -> bool {446		self.len() == 0447	}448	/// For each field, calls callback.449	/// If callback returns false - ends iteration prematurely.450	///451	/// Returns false if ended prematurely452	pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool {453		let mut super_depth = SuperDepth::default();454		self.enum_fields_internal(455			&mut super_depth,456			handler,457			CoreIdx {458				idx: self.0.cores.len(),459			},460		)461	}462	fn enum_fields_internal(463		&self,464		super_depth: &mut SuperDepth,465		handler: &mut EnumFieldsHandler<'_>,466		idx: CoreIdx,467	) -> bool {468		for core in self.0.cores[..idx.idx].iter() {469			if !core.0.enum_fields_core(super_depth, handler) {470				return false;471			}472			super_depth.deepen();473		}474		true475	}476477	pub fn has_field_include_hidden(&self, name: IStr) -> bool {478		self.has_field_include_hidden_idx(479			name,480			CoreIdx {481				idx: self.0.cores.len(),482			},483		)484	}485	fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {486		self.0.cores[..core.idx]487			.iter()488			.rev()489			.any(|v| v.0.has_field_include_hidden(name.clone()))490	}491	pub fn has_field(&self, name: IStr) -> bool {492		match self.field_visibility(name) {493			Some(Visibility::Unhide | Visibility::Normal) => true,494			Some(Visibility::Hidden) | None => false,495		}496	}497	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {498		if include_hidden {499			self.has_field_include_hidden(name)500		} else {501			self.has_field(name)502		}503	}504	pub fn get(&self, key: IStr) -> Result<Option<Val>> {505		self.get_idx(506			key,507			CoreIdx {508				idx: self.0.cores.len(),509			},510		)511	}512513	fn get_idx(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {514		let cache_key = (key.clone(), core);515		{516			let mut cache = self.0.value_cache.borrow_mut();517			// entry_ref candidate?518			match cache.entry(cache_key.clone()) {519				Entry::Occupied(v) => match v.get() {520					CacheValue::Cached(v) => return v.clone(),521					CacheValue::Pending => {522						if !is_asserting(self) {523							bail!(InfiniteRecursionDetected);524						}525					}526				},527				Entry::Vacant(v) => {528					v.insert(CacheValue::Pending);529				}530			};531		}532		let result = self.get_idx_uncached(key, core);533		{534			let mut cache = self.0.value_cache.borrow_mut();535			cache.insert(cache_key, CacheValue::Cached(result.clone()));536		}537		result538	}539	fn get_idx_uncached(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {540		self.run_assertions()?;541		let mut add_stack = Vec::with_capacity(2);542		for (sup, core) in self.0.cores[..core.idx].iter().enumerate().rev() {543			let sup_this = SupThis {544				sup: CoreIdx { idx: sup },545				this: self.clone(),546			};547			if let Some((val, proc)) = core.0.get_for(key.clone(), sup_this)? {548				match proc {549					ValueProcess::None if add_stack.is_empty() => return Ok(Some(val)),550					ValueProcess::None => {551						add_stack.push(val);552						break;553					}554					ValueProcess::SuperPlus => {555						add_stack.push(val);556					}557				}558			}559		}560		if add_stack.is_empty() {561			// None of layers had this field562			return Ok(None);563		} else if add_stack.len() == 1 {564			// A layer had this field, but it wanted this field to be added with super.565			// However, no super had this field, fail-safe566			return Ok(Some(add_stack.pop().expect("single element on stack")));567		}568		let mut values = add_stack.into_iter().rev();569		let init = values.next().expect("at least 2 elements");570571		values572			.try_fold(init, |a, b| evaluate_add_op(&a, &b))573			.map(Some)574575		// self.0.get_raw(key, this)576	}577578	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {579		let Some(value) = self.get(key.clone())? else {580			let suggestions = suggest_object_fields(self, key.clone());581			bail!(NoSuchField(key, suggestions))582		};583		Ok(value)584	}585586	fn field_visibility(&self, field: IStr) -> Option<Visibility> {587		self.field_visibility_idx(588			field,589			CoreIdx {590				idx: self.0.cores.len(),591			},592		)593	}594	fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {595		let mut exists = false;596		for ele in self.0.cores[..core.idx].iter().rev() {597			let vis = ele.0.field_visibility(field.clone());598			match vis {599				Some(Visibility::Unhide | Visibility::Hidden) => return vis,600				Some(Visibility::Normal) => exists = true,601				None => {}602			}603		}604		exists.then_some(Visibility::Normal)605	}606607	pub fn run_assertions(&self) -> Result<()> {608		if self.0.assertions_ran.get() {609			return Ok(());610		}611		if !start_asserting(self) {612			return Ok(());613		}614		for (idx, ele) in self.0.cores.iter().enumerate() {615			let sup_this = SupThis {616				sup: CoreIdx { idx },617				this: self.clone(),618			};619			ele.0.run_assertions_raw(sup_this).inspect_err(|_e| {620				finish_asserting(self);621			})?;622		}623		finish_asserting(self);624		self.0.assertions_ran.set(true);625		Ok(())626	}627628	pub fn iter(629		&self,630		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,631	) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {632		let fields = self.fields(633			#[cfg(feature = "exp-preserve-order")]634			preserve_order,635		);636		fields.into_iter().map(|field| {637			(638				field.clone(),639				self.get(field)640					.map(|opt| opt.expect("iterating over keys, field exists")),641			)642		})643	}644	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {645		if !self.has_field_ex(key.clone(), true) {646			return None;647		}648		let obj = self.clone();649650		Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))651	}652	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {653		let obj = self.clone();654		Thunk!(move || obj.get_or_bail(key))655	}656	pub fn ptr_eq(a: &Self, b: &Self) -> bool {657		Cc::ptr_eq(&a.0, &b.0)658	}659	pub fn downgrade(self) -> WeakObjValue {660		WeakObjValue(self.0.downgrade())661	}662	fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {663		let mut out = FxHashMap::default();664		self.enum_fields(&mut |depth, index, name, visibility| {665			let new_sort_key = FieldSortKey::new(depth, index);666			let entry = out.entry(name);667			let (visible, _) = entry.or_insert((true, new_sort_key));668			match visibility {669				Visibility::Normal => {}670				Visibility::Hidden => {671					*visible = false;672				}673				Visibility::Unhide => {674					*visible = true;675				}676			};677			false678		});679		out680	}681	pub fn fields_ex(682		&self,683		include_hidden: bool,684		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,685	) -> Vec<IStr> {686		#[cfg(feature = "exp-preserve-order")]687		if preserve_order {688			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self689				.fields_visibility()690				.into_iter()691				.filter(|(_, (visible, _))| include_hidden || *visible)692				.enumerate()693				.map(|(idx, (k, (_, sk)))| (k, (sk, idx)))694				.unzip();695			keys.sort_unstable_by_key(|v| v.0);696			// Reorder in-place by resulting indexes697			for i in 0..fields.len() {698				let x = fields[i].clone();699				let mut j = i;700				loop {701					let k = keys[j].1;702					keys[j].1 = j;703					if k == i {704						break;705					}706					fields[j] = fields[k].clone();707					j = k;708				}709				fields[j] = x;710			}711			return fields;712		}713714		let mut fields: Vec<_> = self715			.fields_visibility()716			.into_iter()717			.filter(|(_, (visible, _))| include_hidden || *visible)718			.map(|(k, _)| k)719			.collect();720		fields.sort_unstable();721		fields722	}723	pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec<IStr> {724		self.fields_ex(725			false,726			#[cfg(feature = "exp-preserve-order")]727			preserve_order,728		)729	}730	pub fn values_ex(731		&self,732		include_hidden: bool,733		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,734	) -> ArrValue {735		ArrValue::new(PickObjectValues::new(736			self.clone(),737			self.fields_ex(738				include_hidden,739				#[cfg(feature = "exp-preserve-order")]740				preserve_order,741			),742		))743	}744	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {745		self.values_ex(746			false,747			#[cfg(feature = "exp-preserve-order")]748			preserve_order,749		)750	}751	pub fn key_values_ex(752		&self,753		include_hidden: bool,754		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,755	) -> ArrValue {756		ArrValue::new(PickObjectKeyValues::new(757			self.clone(),758			self.fields_ex(759				include_hidden,760				#[cfg(feature = "exp-preserve-order")]761				preserve_order,762			),763		))764	}765	pub fn key_values(766		&self,767		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,768	) -> ArrValue {769		self.key_values_ex(770			false,771			#[cfg(feature = "exp-preserve-order")]772			preserve_order,773		)774	}775}776777impl OopObject {778	pub fn new(779		this_entries: Cc<FxHashMap<IStr, ObjMember>>,780		assertions: Cc<Vec<CcObjectAssertion>>,781	) -> Self {782		Self {783			this_entries,784			value_cache: RefCell::new(FxHashMap::new()),785			assertions,786		}787	}788}789790impl ObjectCore for OopObject {791	fn enum_fields_core(792		&self,793		super_depth: &mut SuperDepth,794		handler: &mut EnumFieldsHandler<'_>,795	) -> bool {796		for (name, member) in self.this_entries.iter() {797			if handler(798				*super_depth,799				member.original_index,800				name.clone(),801				member.flags.visibility(),802			) {803				return false;804			}805		}806		true807	}808809	fn has_field_include_hidden(&self, name: IStr) -> bool {810		self.this_entries.contains_key(&name)811	}812813	fn get_for(&self, key: IStr, sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>> {814		match self.this_entries.get(&key) {815			Some(k) => Ok(Some((816				k.invoke.evaluate(sup_this)?,817				if k.flags.add() {818					ValueProcess::SuperPlus819				} else {820					ValueProcess::None821				},822			))),823			None => Ok(None),824		}825	}826	fn field_visibility(&self, name: IStr) -> Option<Visibility> {827		Some(self.this_entries.get(&name)?.flags.visibility())828	}829830	fn run_assertions_raw(&self, sup_this: SupThis) -> Result<()> {831		if self.assertions.is_empty() {832			return Ok(());833		}834		for assertion in self.assertions.iter() {835			assertion.0.run(sup_this.clone())?;836		}837		Ok(())838	}839}840841#[allow(clippy::module_name_repetitions)]842pub struct ObjValueBuilder {843	sup: Option<ObjValue>,844	map: FxHashMap<IStr, ObjMember>,845	assertions: Vec<CcObjectAssertion>,846	next_field_index: FieldIndex,847}848impl ObjValueBuilder {849	pub fn new() -> Self {850		Self::with_capacity(0)851	}852	pub fn with_capacity(capacity: usize) -> Self {853		Self {854			sup: None,855			map: FxHashMap::with_capacity(capacity),856			assertions: Vec::new(),857			next_field_index: FieldIndex::default(),858		}859	}860	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {861		self.assertions.reserve_exact(capacity);862		self863	}864	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {865		self.sup = Some(super_obj);866		self867	}868869	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {870		self.assertions.push(CcObjectAssertion::new(assertion));871		self872	}873	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {874		let field_index = self.next_field_index;875		self.next_field_index = self.next_field_index.next();876		ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)877	}878	/// Preset for common method definiton pattern:879	/// Create a hidden field with the function value.880	///881	/// `.field(name).hide().value(Val::function(value))`882	pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {883		self.field(name).hide().value(Val::Func(value.into()));884		self885	}886	pub fn try_method(887		&mut self,888		name: impl Into<IStr>,889		value: impl Into<FuncVal>,890	) -> Result<&mut Self> {891		self.field(name).hide().try_value(Val::Func(value.into()))?;892		Ok(self)893	}894895	pub fn build(self) -> ObjValue {896		if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() {897			return ObjValue::new_empty();898		}899		let res = ObjValue::new(OopObject::new(Cc::new(self.map), Cc::new(self.assertions)));900		self.sup.map(|sup| res.extend_from(sup)).unwrap_or(res)901	}902}903impl Default for ObjValueBuilder {904	fn default() -> Self {905		Self::with_capacity(0)906	}907}908909#[allow(clippy::module_name_repetitions)]910#[must_use = "value not added unless binding() was called"]911pub struct ObjMemberBuilder<Kind> {912	kind: Kind,913	name: IStr,914	add: bool,915	visibility: Visibility,916	original_index: FieldIndex,917	location: Option<Span>,918}919920#[allow(clippy::missing_const_for_fn)]921impl<Kind> ObjMemberBuilder<Kind> {922	pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self {923		Self {924			kind,925			name,926			original_index,927			add: false,928			visibility: Visibility::Normal,929			location: None,930		}931	}932933	pub const fn with_add(mut self, add: bool) -> Self {934		self.add = add;935		self936	}937	pub fn add(self) -> Self {938		self.with_add(true)939	}940	pub fn with_visibility(mut self, visibility: Visibility) -> Self {941		self.visibility = visibility;942		self943	}944	pub fn hide(self) -> Self {945		self.with_visibility(Visibility::Hidden)946	}947	pub fn with_location(mut self, location: Span) -> Self {948		self.location = Some(location);949		self950	}951	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {952		(953			self.kind,954			self.name,955			ObjMember {956				flags: ObjFieldFlags::new(self.add, self.visibility),957				original_index: self.original_index,958				invoke: binding,959				location: self.location,960			},961		)962	}963}964965pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);966impl ObjMemberBuilder<ValueBuilder<'_>> {967	/// Inserts value, replacing if it is already defined968	pub fn value(self, value: impl Into<Val>) {969		let (receiver, name, member) =970			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));971		let entry = receiver.0.map.entry(name);972		entry.insert_entry(member);973	}974975	/// Tries to insert value, returns an error if it was already defined976	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {977		self.try_thunk(Thunk::evaluated(value.into()))978	}979	pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {980		self.binding(MaybeUnbound::Bound(value.into()))981	}982	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {983		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)))984	}985	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {986		let (receiver, name, member) = self.build_member(binding);987		let location = member.location.clone();988		let old = receiver.0.map.insert(name.clone(), member);989		if old.is_some() {990			in_frame(991				CallLocation(location.as_ref()),992				|| format!("field <{}> initializtion", name.clone()),993				|| bail!(DuplicateFieldName(name.clone())),994			)?;995		}996		Ok(())997	}998}9991000pub struct ExtendBuilder<'v>(&'v mut ObjValue);1001impl ObjMemberBuilder<ExtendBuilder<'_>> {1002	pub fn value(self, value: impl Into<Val>) {1003		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1004	}1005	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) {1006		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)));1007	}1008	pub fn binding(self, binding: MaybeUnbound) {1009		let (receiver, name, member) = self.build_member(binding);1010		let new = receiver.0.clone();1011		*receiver.0 = new.extend_with_raw_member(name, member);1012	}1013}
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	ops::ControlFlow,9};1011use educe::Educe;12use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};13use jrsonnet_interner::IStr;14use jrsonnet_parser::{Span, Visibility};15use rustc_hash::{FxHashMap, FxHashSet};1617use crate::{18	arr::{PickObjectKeyValues, PickObjectValues},19	bail,20	error::{suggest_object_fields, ErrorKind::*},21	function::{CallLocation, FuncVal},22	gc::WithCapacityExt as _,23	identity_hash, in_frame,24	operator::evaluate_add_op,25	val::ArrValue,26	CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,27};2829#[cfg(not(feature = "exp-preserve-order"))]30mod ordering {31	#![allow(32		// This module works as stub for preserve-order feature33		clippy::unused_self,34	)]3536	use jrsonnet_gcmodule::Trace;3738	#[derive(Clone, Copy, Default, Debug, Trace)]39	pub struct FieldIndex(());40	impl FieldIndex {41		pub const fn next(self) -> Self {42			Self(())43		}44	}4546	#[derive(Clone, Copy, Default, Debug, Trace)]47	pub struct SuperDepth(());48	impl SuperDepth {49		pub(super) fn deepen(self) {}50	}5152	#[derive(Clone, Copy, Debug)]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(super) fn deepen(&mut self) {79			self.0 += 180		}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 fmt::Formatter<'_>) -> 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}141142cc_dyn!(CcObjectAssertion, ObjectAssertion);143pub trait ObjectAssertion: Trace {144	fn run(&self, sup_this: SupThis) -> Result<()>;145}146147// Field => This148149#[derive(Trace, Debug)]150enum CacheValue {151	Cached(Result<Option<Val>>),152	Pending,153}154155#[allow(clippy::module_name_repetitions)]156#[derive(Trace, Default)]157#[trace(tracking(force))]158pub struct OopObject {159	assertions: Vec<CcObjectAssertion>,160	this_entries: FxHashMap<IStr, ObjMember>,161}162impl Debug for OopObject {163	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {164		f.debug_struct("OopObject")165			.field("this_entries", &self.this_entries)166			.finish_non_exhaustive()167	}168}169impl OopObject {170	fn is_empty(&self) -> bool {171		self.assertions.is_empty() && self.this_entries.is_empty()172	}173}174175type EnumFieldsHandler<'a> =176	dyn FnMut(SuperDepth, FieldIndex, IStr, EnumFields) -> ControlFlow<()> + 'a;177178pub enum EnumFields {179	Normal(Visibility),180	Omit,181}182183#[derive(Trace, Clone)]184pub enum GetFor {185	// Return value186	Final(Val),187	// Continue iterating over cores, add current value to sum stack188	SuperPlus(Val),189	// Ignore the field value, stop at this layer instead190	Omit,191	NotFound,192}193194#[derive(Acyclic, Clone)]195pub enum FieldVisibility {196	Found(Visibility),197	Omit,198	NotFound,199}200201#[derive(Acyclic, Clone)]202pub enum HasFieldIncludeHidden {203	Exists,204	NotFound,205	Omit,206}207208pub trait ObjectCore: Trace + Any + Debug {209	// If callback returns false, iteration stops, and this call returns false.210	fn enum_fields_core(211		&self,212		super_depth: &mut SuperDepth,213		handler: &mut EnumFieldsHandler<'_>,214	) -> bool;215216	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden;217218	fn get_for_core(&self, key: IStr, sup_this: SupThis) -> Result<GetFor>;219	fn field_visibility_core(&self, field: IStr) -> FieldVisibility;220221	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()>;222}223224#[derive(Clone, Trace)]225pub struct WeakObjValue(#[trace(skip)] Weak<ObjValueInner>);226impl Debug for WeakObjValue {227	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {228		f.debug_tuple("WeakObjValue").finish()229	}230}231232impl PartialEq for WeakObjValue {233	fn eq(&self, other: &Self) -> bool {234		Weak::ptr_eq(&self.0, &other.0)235	}236}237238impl Eq for WeakObjValue {}239impl Hash for WeakObjValue {240	fn hash<H: Hasher>(&self, hasher: &mut H) {241		// Safety: usize is POD242		let addr = unsafe { *std::ptr::addr_of!(self.0).cast() };243		hasher.write_usize(addr);244	}245}246247cc_dyn!(248	#[derive(Clone, Debug)]249	CcObjectCore, ObjectCore,250	pub fn new() {...}251);252#[derive(Trace, Educe)]253#[educe(Debug)]254struct ObjValueInner {255	cores: Vec<CcObjectCore>,256	assertions_ran: Cell<bool>,257	value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,258}259260thread_local! {261	static RUNNING_ASSERTIONS: RefCell<FxHashSet<ObjValue>> = RefCell::default();262}263fn is_asserting(obj: &ObjValue) -> bool {264	RUNNING_ASSERTIONS.with_borrow(|v| v.contains(obj))265}266/// Returns false if already asserting267fn start_asserting(obj: &ObjValue) -> bool {268	RUNNING_ASSERTIONS.with_borrow_mut(|v| v.insert(obj.clone()))269}270fn finish_asserting(obj: &ObjValue) {271	RUNNING_ASSERTIONS.with_borrow_mut(|v| {272		let r = v.remove(obj);273		debug_assert!(274			r,275			"finish_asserting was called before start_asserting or twice"276		);277	});278}279280thread_local! {281	static EMPTY_OBJ: ObjValue = ObjValue(Cc::new(ObjValueInner {282		cores: vec![],283		assertions_ran: Cell::new(true),284		value_cache: Default::default(),285	}))286}287288#[allow(clippy::module_name_repetitions)]289#[derive(Clone, Trace, Debug, Educe)]290#[educe(PartialEq, Hash, Eq)]291pub struct ObjValue(292	#[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc<ObjValueInner>,293);294295impl ObjValue {296	pub fn empty() -> Self {297		EMPTY_OBJ.with(|v| v.clone())298	}299	pub fn is_empty(&self) -> bool {300		self.0.cores.is_empty() || self.len() == 0301	}302}303304#[derive(Trace, Debug)]305struct StandaloneSuperCore {306	sup: CoreIdx,307	this: ObjValue,308}309impl ObjectCore for StandaloneSuperCore {310	fn enum_fields_core(311		&self,312		super_depth: &mut SuperDepth,313		handler: &mut EnumFieldsHandler<'_>,314	) -> bool {315		self.this.enum_fields_idx(super_depth, handler, self.sup)316	}317318	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {319		if self.this.has_field_include_hidden_idx(name, self.sup) {320			HasFieldIncludeHidden::Exists321		} else {322			HasFieldIncludeHidden::NotFound323		}324	}325326	fn get_for_core(&self, key: IStr, _sup_this: SupThis) -> Result<GetFor> {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}347impl ObjectCore for OmitFieldsCore {348	fn enum_fields_core(349		&self,350		super_depth: &mut SuperDepth,351		handler: &mut EnumFieldsHandler<'_>,352	) -> bool {353		let mut fi = FieldIndex::default();354		for f in &self.omit {355			if let ControlFlow::Break(()) = handler(*super_depth, fi, f.clone(), EnumFields::Omit) {356				return false;357			}358			fi = fi.next();359		}360		true361	}362363	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {364		if self.omit.contains(&name) {365			return HasFieldIncludeHidden::Omit;366		}367		HasFieldIncludeHidden::NotFound368	}369370	fn get_for_core(&self, key: IStr, _sup_this: SupThis) -> Result<GetFor> {371		if self.omit.contains(&key) {372			return Ok(GetFor::Omit);373		}374		Ok(GetFor::NotFound)375	}376377	fn field_visibility_core(&self, field: IStr) -> FieldVisibility {378		if self.omit.contains(&field) {379			return FieldVisibility::Omit;380		}381		FieldVisibility::NotFound382	}383384	fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {385		Ok(())386	}387}388389#[derive(Hash, PartialEq, Eq, Trace, Clone, Copy, Debug)]390struct CoreIdx {391	idx: usize,392}393impl CoreIdx {394	fn super_exists(self) -> bool {395		self.idx != 0396	}397}398#[derive(Trace, Clone, PartialEq, Eq, Hash, Debug)]399pub struct SupThis {400	sup: CoreIdx,401	this: ObjValue,402}403impl SupThis {404	pub fn has_super(&self) -> bool {405		self.sup.super_exists()406	}407	/// Implementation of `"field" in super` operation,408	/// works faster than standalone super path.409	///410	/// In case of no `super` existence, returns false.411	pub fn field_in_super(&self, field: IStr) -> bool {412		self.this.has_field_include_hidden_idx(field, self.sup)413	}414	/// Implementation of `super.field` operation,415	/// works faster than standalone super path.416	///417	/// In case of no `super` existence, returns `NoSuperFound`418	pub fn get_super(&self, field: IStr) -> Result<Option<Val>> {419		if !self.sup.super_exists() {420			bail!(NoSuperFound);421		}422		self.this.get_idx(field, self.sup)423	}424	/// `super` with `self` overriden for top-level lookups.425	/// Exists when super appears outside of `super.field`/`"field" in super` expressions426	/// Exclusive to jrsonnet.427	///428	/// Might return `NoSuperFound` error.429	pub fn standalone_super(&self) -> Result<ObjValue> {430		if !self.sup.super_exists() {431			bail!(NoSuperFound)432		}433		let mut out = ObjValue::builder();434		out.reserve_cores(1).extend_with_core(StandaloneSuperCore {435			sup: self.sup,436			this: self.this.clone(),437		});438		Ok(out.build())439	}440	pub fn this(&self) -> &ObjValue {441		&self.this442	}443	pub fn downgrade(self) -> WeakSupThis {444		WeakSupThis {445			sup: self.sup,446			this: self.this.downgrade(),447		}448	}449}450#[derive(Trace, PartialEq, Eq, Hash, Debug)]451pub struct WeakSupThis {452	sup: CoreIdx,453	this: WeakObjValue,454}455456impl ObjValue {457	pub fn builder() -> ObjValueBuilder {458		ObjValueBuilder::new()459	}460	pub fn builder_with_capacity(capacity: usize) -> ObjValueBuilder {461		ObjValueBuilder::with_capacity(capacity)462	}463	pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self {464		let mut out = ObjValueBuilder::with_capacity(1);465		out.with_super(self);466		let mut member = out.field(key);467		if value.flags.add() {468			member = member.add();469		}470		if let Some(loc) = value.location {471			member = member.with_location(loc);472		}473		let _ = member474			.with_visibility(value.flags.visibility())475			.binding(value.invoke);476		out.build()477	}478	pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {479		ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())480	}481482	pub fn extend(&mut self) -> ObjValueBuilder {483		let mut out = ObjValueBuilder::new();484		out.with_super(self.clone());485		out486	}487488	#[must_use]489	pub fn extend_from(&self, sup: Self) -> Self {490		let mut cores = sup.0.cores.clone();491		cores.extend(self.0.cores.iter().cloned());492		ObjValue(Cc::new(ObjValueInner {493			cores,494			value_cache: RefCell::default(),495			assertions_ran: Cell::new(false),496		}))497	}498	// #[must_use]499	// pub fn with_this(&self, this: Self) -> Self {500	// 	self.0.with_this(self.clone(), this)501	// }502	/// Returns amount of visible object fields503	/// If object only contains hidden fields - may return zero.504	pub fn len(&self) -> usize {505		self.fields_visibility()506			.iter()507			.filter(|(_, (visible, _))| *visible)508			.count()509	}510	/// For each field, calls callback.511	/// If callback returns false - ends iteration prematurely.512	///513	/// Returns false if ended prematurely514	pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool {515		let mut super_depth = SuperDepth::default();516		self.enum_fields_idx(517			&mut super_depth,518			handler,519			CoreIdx {520				idx: self.0.cores.len(),521			},522		)523	}524	fn enum_fields_idx(525		&self,526		super_depth: &mut SuperDepth,527		handler: &mut EnumFieldsHandler<'_>,528		idx: CoreIdx,529	) -> bool {530		for core in self.0.cores[..idx.idx].iter() {531			if !core.0.enum_fields_core(super_depth, handler) {532				return false;533			}534			super_depth.deepen();535		}536		true537	}538539	pub fn has_field_include_hidden(&self, name: IStr) -> bool {540		self.has_field_include_hidden_idx(541			name,542			CoreIdx {543				idx: self.0.cores.len(),544			},545		)546	}547	fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {548		for ele in self.0.cores[..core.idx].iter().rev() {549			match ele.0.has_field_include_hidden_core(name.clone()) {550				HasFieldIncludeHidden::Exists => return true,551				HasFieldIncludeHidden::NotFound => {}552				HasFieldIncludeHidden::Omit => break,553			}554		}555		false556	}557	pub fn has_field(&self, name: IStr) -> bool {558		match self.field_visibility(name) {559			Some(Visibility::Unhide | Visibility::Normal) => true,560			Some(Visibility::Hidden) | None => false,561		}562	}563	pub fn has_field_ex(&self, name: IStr, include_hidden: bool) -> bool {564		if include_hidden {565			self.has_field_include_hidden(name)566		} else {567			self.has_field(name)568		}569	}570	pub fn get(&self, key: IStr) -> Result<Option<Val>> {571		self.get_idx(572			key,573			CoreIdx {574				idx: self.0.cores.len(),575			},576		)577	}578579	fn get_idx(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {580		let cache_key = (key.clone(), core);581		{582			let mut cache = self.0.value_cache.borrow_mut();583			// entry_ref candidate?584			match cache.entry(cache_key.clone()) {585				Entry::Occupied(v) => match v.get() {586					CacheValue::Cached(v) => return v.clone(),587					CacheValue::Pending => {588						if !is_asserting(self) {589							bail!(InfiniteRecursionDetected);590						}591					}592				},593				Entry::Vacant(v) => {594					v.insert(CacheValue::Pending);595				}596			};597		}598		let result = self.get_idx_uncached(key, core);599		{600			let mut cache = self.0.value_cache.borrow_mut();601			cache.insert(cache_key, CacheValue::Cached(result.clone()));602		}603		result604	}605	fn get_idx_uncached(&self, key: IStr, core: CoreIdx) -> Result<Option<Val>> {606		self.run_assertions()?;607		let mut add_stack = Vec::with_capacity(2);608		for (sup, core) in self.0.cores[..core.idx].iter().enumerate().rev() {609			let sup_this = SupThis {610				sup: CoreIdx { idx: sup },611				this: self.clone(),612			};613			match core.0.get_for_core(key.clone(), sup_this)? {614				GetFor::Final(val) if add_stack.is_empty() => return Ok(Some(val)),615				GetFor::Final(val) => {616					add_stack.push(val);617					break;618				}619				GetFor::SuperPlus(val) => {620					add_stack.push(val);621				}622				GetFor::Omit => {623					break;624				}625				GetFor::NotFound => {626					continue;627				}628			}629		}630		if add_stack.is_empty() {631			// None of layers had this field632			return Ok(None);633		} else if add_stack.len() == 1 {634			// A layer had this field, but it wanted this field to be added with super.635			// However, no super had this field, fail-safe636			return Ok(Some(add_stack.pop().expect("single element on stack")));637		}638		let mut values = add_stack.into_iter().rev();639		let init = values.next().expect("at least 2 elements");640641		values642			.try_fold(init, |a, b| evaluate_add_op(&a, &b))643			.map(Some)644645		// self.0.get_raw(key, this)646	}647648	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {649		let Some(value) = self.get(key.clone())? else {650			let suggestions = suggest_object_fields(self, key.clone());651			bail!(NoSuchField(key, suggestions))652		};653		Ok(value)654	}655656	fn field_visibility(&self, field: IStr) -> Option<Visibility> {657		self.field_visibility_idx(658			field,659			CoreIdx {660				idx: self.0.cores.len(),661			},662		)663	}664	fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {665		let mut exists = false;666		for ele in self.0.cores[..core.idx].iter().rev() {667			let vis = ele.0.field_visibility_core(field.clone());668			match vis {669				FieldVisibility::Found(vis @ (Visibility::Unhide | Visibility::Hidden)) => {670					return Some(vis)671				}672				FieldVisibility::Found(Visibility::Normal) => exists = true,673				FieldVisibility::NotFound => {}674				FieldVisibility::Omit => break,675			}676		}677		exists.then_some(Visibility::Normal)678	}679680	pub fn run_assertions(&self) -> Result<()> {681		if self.0.assertions_ran.get() {682			return Ok(());683		}684		if !start_asserting(self) {685			return Ok(());686		}687		for (idx, ele) in self.0.cores.iter().enumerate() {688			let sup_this = SupThis {689				sup: CoreIdx { idx },690				this: self.clone(),691			};692			ele.0.run_assertions_core(sup_this).inspect_err(|_e| {693				finish_asserting(self);694			})?;695		}696		finish_asserting(self);697		self.0.assertions_ran.set(true);698		Ok(())699	}700701	pub fn iter(702		&self,703		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,704	) -> impl Iterator<Item = (IStr, Result<Val>)> + '_ {705		let fields = self.fields(706			#[cfg(feature = "exp-preserve-order")]707			preserve_order,708		);709		fields.into_iter().map(|field| {710			(711				field.clone(),712				self.get(field)713					.map(|opt| opt.expect("iterating over keys, field exists")),714			)715		})716	}717	pub fn get_lazy(&self, key: IStr) -> Option<Thunk<Val>> {718		if !self.has_field_ex(key.clone(), true) {719			return None;720		}721		let obj = self.clone();722723		Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))724	}725	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {726		let obj = self.clone();727		Thunk!(move || obj.get_or_bail(key))728	}729	pub fn ptr_eq(a: &Self, b: &Self) -> bool {730		Cc::ptr_eq(&a.0, &b.0)731	}732	pub fn downgrade(self) -> WeakObjValue {733		WeakObjValue(self.0.downgrade())734	}735	fn fields_visibility(&self) -> FxHashMap<IStr, (bool, FieldSortKey)> {736		let mut out = FxHashMap::default();737		self.enum_fields(&mut |depth, index, name, visibility| {738			let new_sort_key = FieldSortKey::new(depth, index);739			let entry = out.entry(name);740			if matches!(visibility, EnumFields::Omit) {741				if let Entry::Occupied(v) = entry {742					v.remove();743				}744				return ControlFlow::Continue(());745			}746			let (visible, _) = entry.or_insert((true, new_sort_key));747			match visibility {748				EnumFields::Omit => unreachable!(),749				EnumFields::Normal(Visibility::Normal) => {}750				EnumFields::Normal(Visibility::Hidden) => {751					*visible = false;752				}753				EnumFields::Normal(Visibility::Unhide) => {754					*visible = true;755				}756			};757			return ControlFlow::Continue(());758		});759		out760	}761	pub fn fields_ex(762		&self,763		include_hidden: bool,764		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,765	) -> Vec<IStr> {766		#[cfg(feature = "exp-preserve-order")]767		if preserve_order {768			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self769				.fields_visibility()770				.into_iter()771				.filter(|(_, (visible, _))| include_hidden || *visible)772				.enumerate()773				.map(|(idx, (k, (_, sk)))| (k, (sk, idx)))774				.unzip();775			keys.sort_unstable_by_key(|v| v.0);776			// Reorder in-place by resulting indexes777			for i in 0..fields.len() {778				let x = fields[i].clone();779				let mut j = i;780				loop {781					let k = keys[j].1;782					keys[j].1 = j;783					if k == i {784						break;785					}786					fields[j] = fields[k].clone();787					j = k;788				}789				fields[j] = x;790			}791			return fields;792		}793794		let mut fields: Vec<_> = self795			.fields_visibility()796			.into_iter()797			.filter(|(_, (visible, _))| include_hidden || *visible)798			.map(|(k, _)| k)799			.collect();800		fields.sort_unstable();801		fields802	}803	pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec<IStr> {804		self.fields_ex(805			false,806			#[cfg(feature = "exp-preserve-order")]807			preserve_order,808		)809	}810	pub fn values_ex(811		&self,812		include_hidden: bool,813		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,814	) -> ArrValue {815		ArrValue::new(PickObjectValues::new(816			self.clone(),817			self.fields_ex(818				include_hidden,819				#[cfg(feature = "exp-preserve-order")]820				preserve_order,821			),822		))823	}824	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {825		self.values_ex(826			false,827			#[cfg(feature = "exp-preserve-order")]828			preserve_order,829		)830	}831	pub fn key_values_ex(832		&self,833		include_hidden: bool,834		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,835	) -> ArrValue {836		ArrValue::new(PickObjectKeyValues::new(837			self.clone(),838			self.fields_ex(839				include_hidden,840				#[cfg(feature = "exp-preserve-order")]841				preserve_order,842			),843		))844	}845	pub fn key_values(846		&self,847		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,848	) -> ArrValue {849		self.key_values_ex(850			false,851			#[cfg(feature = "exp-preserve-order")]852			preserve_order,853		)854	}855}856857impl OopObject {858	pub fn new(859		this_entries: FxHashMap<IStr, ObjMember>,860		assertions: Vec<CcObjectAssertion>,861	) -> Self {862		Self {863			this_entries,864			assertions,865		}866	}867}868869impl ObjectCore for OopObject {870	fn enum_fields_core(871		&self,872		super_depth: &mut SuperDepth,873		handler: &mut EnumFieldsHandler<'_>,874	) -> bool {875		for (name, member) in self.this_entries.iter() {876			if matches!(877				handler(878					*super_depth,879					member.original_index,880					name.clone(),881					EnumFields::Normal(member.flags.visibility()),882				),883				ControlFlow::Break(())884			) {885				return false;886			}887		}888		true889	}890891	fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {892		if self.this_entries.contains_key(&name) {893			HasFieldIncludeHidden::Exists894		} else {895			HasFieldIncludeHidden::NotFound896		}897	}898899	fn get_for_core(&self, key: IStr, sup_this: SupThis) -> Result<GetFor> {900		match self.this_entries.get(&key) {901			Some(k) => {902				let v = k.invoke.evaluate(sup_this)?;903				Ok(if k.flags.add() {904					GetFor::SuperPlus(v)905				} else {906					GetFor::Final(v)907				})908			}909			None => Ok(GetFor::NotFound),910		}911	}912	fn field_visibility_core(&self, name: IStr) -> FieldVisibility {913		match self.this_entries.get(&name) {914			Some(f) => FieldVisibility::Found(f.flags.visibility()),915			None => FieldVisibility::NotFound,916		}917	}918919	fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {920		if self.assertions.is_empty() {921			return Ok(());922		}923		for assertion in self.assertions.iter() {924			assertion.0.run(sup_this.clone())?;925		}926		Ok(())927	}928}929930#[allow(clippy::module_name_repetitions)]931pub struct ObjValueBuilder {932	sup: Vec<CcObjectCore>,933934	new: OopObject,935	next_field_index: FieldIndex,936}937impl ObjValueBuilder {938	pub fn new() -> Self {939		Self::with_capacity(0)940	}941	pub fn with_capacity(capacity: usize) -> Self {942		Self {943			sup: vec![],944			new: OopObject {945				assertions: vec![],946				this_entries: FxHashMap::with_capacity(capacity),947			},948			next_field_index: FieldIndex::default(),949		}950	}951	pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {952		self.sup.reserve_exact(capacity);953		self954	}955	pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {956		self.new.assertions.reserve_exact(capacity);957		self958	}959	pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {960		self.sup = super_obj.0.cores.clone();961		self962	}963964	pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {965		self.new.assertions.push(CcObjectAssertion::new(assertion));966		self967	}968	pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {969		let field_index = self.next_field_index;970		self.next_field_index = self.next_field_index.next();971		ObjMemberBuilder::new(ValueBuilder(self), name.into(), field_index)972	}973	/// Preset for common method definiton pattern:974	/// Create a hidden field with the function value.975	///976	/// `.field(name).hide().value(Val::function(value))`977	pub fn method(&mut self, name: impl Into<IStr>, value: impl Into<FuncVal>) -> &mut Self {978		self.field(name).hide().value(Val::Func(value.into()));979		self980	}981	pub fn try_method(982		&mut self,983		name: impl Into<IStr>,984		value: impl Into<FuncVal>,985	) -> Result<&mut Self> {986		self.field(name).hide().try_value(Val::Func(value.into()))?;987		Ok(self)988	}989990	pub fn extend_with_core(&mut self, core: impl ObjectCore) {991		self.commit();992		self.sup.push(CcObjectCore::new(core));993	}994995	fn commit(&mut self) {996		if !self.new.is_empty() {997			self.sup.push(CcObjectCore::new(mem::take(&mut self.new)));998		}999		self.next_field_index = FieldIndex::default();1000	}10011002	pub fn with_fields_omitted(&mut self, omit: FxHashSet<IStr>) {1003		self.commit();1004		self.sup.push(CcObjectCore::new(OmitFieldsCore { omit }));1005	}10061007	pub fn build(mut self) -> ObjValue {1008		self.commit();1009		if self.sup.is_empty() {1010			return ObjValue::empty();1011		}1012		ObjValue(Cc::new(ObjValueInner {1013			cores: self.sup,1014			assertions_ran: Cell::new(false),1015			value_cache: Default::default(),1016		}))1017	}1018}1019impl Default for ObjValueBuilder {1020	fn default() -> Self {1021		Self::with_capacity(0)1022	}1023}10241025#[allow(clippy::module_name_repetitions)]1026#[must_use = "value not added unless binding() was called"]1027pub struct ObjMemberBuilder<Kind> {1028	kind: Kind,1029	name: IStr,1030	add: bool,1031	visibility: Visibility,1032	original_index: FieldIndex,1033	location: Option<Span>,1034}10351036#[allow(clippy::missing_const_for_fn)]1037impl<Kind> ObjMemberBuilder<Kind> {1038	pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self {1039		Self {1040			kind,1041			name,1042			original_index,1043			add: false,1044			visibility: Visibility::Normal,1045			location: None,1046		}1047	}10481049	pub const fn with_add(mut self, add: bool) -> Self {1050		self.add = add;1051		self1052	}1053	pub fn add(self) -> Self {1054		self.with_add(true)1055	}1056	pub fn with_visibility(mut self, visibility: Visibility) -> Self {1057		self.visibility = visibility;1058		self1059	}1060	pub fn hide(self) -> Self {1061		self.with_visibility(Visibility::Hidden)1062	}1063	pub fn with_location(mut self, location: Span) -> Self {1064		self.location = Some(location);1065		self1066	}1067	fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) {1068		(1069			self.kind,1070			self.name,1071			ObjMember {1072				flags: ObjFieldFlags::new(self.add, self.visibility),1073				original_index: self.original_index,1074				invoke: binding,1075				location: self.location,1076			},1077		)1078	}1079}10801081pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);1082impl ObjMemberBuilder<ValueBuilder<'_>> {1083	/// Inserts value, replacing if it is already defined1084	pub fn value(self, value: impl Into<Val>) {1085		let (receiver, name, member) =1086			self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1087		let entry = receiver.0.new.this_entries.entry(name);1088		entry.insert_entry(member);1089	}10901091	/// Tries to insert value, returns an error if it was already defined1092	pub fn try_value(self, value: impl Into<Val>) -> Result<()> {1093		self.try_thunk(Thunk::evaluated(value.into()))1094	}1095	pub fn try_thunk(self, value: impl Into<Thunk<Val>>) -> Result<()> {1096		self.binding(MaybeUnbound::Bound(value.into()))1097	}1098	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) -> Result<()> {1099		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)))1100	}1101	pub fn binding(self, binding: MaybeUnbound) -> Result<()> {1102		let (receiver, name, member) = self.build_member(binding);1103		let location = member.location.clone();1104		let old = receiver.0.new.this_entries.insert(name.clone(), member);1105		if old.is_some() {1106			in_frame(1107				CallLocation(location.as_ref()),1108				|| format!("field <{}> initializtion", name.clone()),1109				|| bail!(DuplicateFieldName(name.clone())),1110			)?;1111		}1112		Ok(())1113	}1114}11151116pub struct ExtendBuilder<'v>(&'v mut ObjValue);1117impl ObjMemberBuilder<ExtendBuilder<'_>> {1118	pub fn value(self, value: impl Into<Val>) {1119		self.binding(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1120	}1121	pub fn bindable(self, bindable: impl Unbound<Bound = Val>) {1122		self.binding(MaybeUnbound::Unbound(CcUnbound::new(bindable)));1123	}1124	pub fn binding(self, binding: MaybeUnbound) {1125		let (receiver, name, member) = self.build_member(binding);1126		let new = receiver.0.clone();1127		*receiver.0 = new.extend_with_raw_member(name, member);1128	}1129}
modifiedcrates/jrsonnet-stdlib/src/manifest/xml.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/manifest/xml.rs
+++ b/crates/jrsonnet-stdlib/src/manifest/xml.rs
@@ -62,10 +62,10 @@
 			if let Val::Obj(attrs) = maybe_attrs {
 				(true, attrs)
 			} else {
-				(false, ObjValue::new_empty())
+				(false, ObjValue::empty())
 			}
 		} else {
-			(false, ObjValue::new_empty())
+			(false, ObjValue::empty())
 		};
 		Ok(Self::Tag {
 			tag,
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -172,7 +172,7 @@
 	let Some(patch) = patch.as_obj() else {
 		return Ok(patch);
 	};
-	let target = target.as_obj().unwrap_or_else(|| ObjValue::new_empty());
+	let target = target.as_obj().unwrap_or_else(|| ObjValue::empty());
 	let target_fields = target
 		.fields(
 			// FIXME: Makes no sense to preserve order for BTreeSet, it would be better to use IndexSet here?
modifiedcrates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -1,8 +1,9 @@
 use jrsonnet_evaluator::{
 	function::builtin,
+	gc::WithCapacityExt,
 	rustc_hash::FxHashSet,
 	val::{ArrValue, Val},
-	IStr, MaybeUnbound, ObjValue, ObjValueBuilder, Thunk,
+	IStr, ObjValue, ObjValueBuilder,
 };
 
 #[builtin]
@@ -156,43 +157,11 @@
 }
 
 #[builtin]
-pub fn builtin_object_remove_key(
-	obj: ObjValue,
-	key: IStr,
-
-	// Standard implementation uses std.objectFields without such argument, we can't
-	// assume order preservation should always be enabled/disabled
-	#[default(false)]
-	#[cfg(feature = "exp-preserve-order")]
-	preserve_order: bool,
-) -> ObjValue {
-	let mut new_obj = ObjValueBuilder::with_capacity(obj.len() - 1);
-	let all_fields = obj.fields_ex(
-		true,
-		#[cfg(feature = "exp-preserve-order")]
-		preserve_order,
-	);
-	let visible_fields = obj
-		.fields_ex(
-			false,
-			#[cfg(feature = "exp-preserve-order")]
-			preserve_order,
-		)
-		.into_iter()
-		.collect::<FxHashSet<_>>();
-
-	for field in &all_fields {
-		if *field == key {
-			continue;
-		}
-		let mut b = new_obj.field(field.clone());
-		if !visible_fields.contains(&field) {
-			b = b.hide();
-		}
-		let _ = b.binding(MaybeUnbound::Bound(Thunk::result(
-			obj.get(field.clone()).transpose().expect("field exists"),
-		)));
-	}
+pub fn builtin_object_remove_key(obj: ObjValue, key: IStr) -> ObjValue {
+	let mut omit = FxHashSet::with_capacity(1);
+	omit.insert(key);
 
-	new_obj.build()
+	let mut out = ObjValueBuilder::new();
+	out.with_super(obj).with_fields_omitted(omit);
+	out.build()
 }
modifiedtests/tests/as_native.rsdiffbeforeafterboth
--- a/tests/tests/as_native.rs
+++ b/tests/tests/as_native.rs
@@ -1,4 +1,4 @@
-use jrsonnet_evaluator::{trace::PathResolver, FileImportResolver, Result, State};
+use jrsonnet_evaluator::{FileImportResolver, Result, State, trace::PathResolver};
 use jrsonnet_stdlib::ContextInitializer;
 
 mod common;
modifiedtests/tests/builtin.rsdiffbeforeafterboth
--- a/tests/tests/builtin.rs
+++ b/tests/tests/builtin.rs
@@ -1,11 +1,11 @@
 mod common;
 
 use jrsonnet_evaluator::{
-	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
+	ContextBuilder, ContextInitializer, FileImportResolver, Result, State, Thunk, Val,
+	function::{CallLocation, FuncVal, builtin, builtin::Builtin},
 	parser::Source,
 	trace::PathResolver,
 	typed::Typed,
-	ContextBuilder, ContextInitializer, FileImportResolver, Result, State, Thunk, Val,
 };
 use jrsonnet_gcmodule::Trace;
 use jrsonnet_stdlib::ContextInitializer as StdContextInitializer;
@@ -18,11 +18,8 @@
 #[test]
 fn basic_function() -> Result<()> {
 	let a: a = a {};
-	let v = u32::from_untyped(a.call(
-		ContextBuilder::new().build(),
-		CallLocation::native(),
-		&(),
-	)?)?;
+	let v =
+		u32::from_untyped(a.call(ContextBuilder::new().build(), CallLocation::native(), &())?)?;
 
 	ensure_eq!(v, 1);
 	Ok(())
modifiedtests/tests/common.rsdiffbeforeafterboth
--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -1,8 +1,8 @@
 use jrsonnet_evaluator::{
+	ContextBuilder, ContextInitializer as ContextInitializerT, ObjValueBuilder, Result, Thunk, Val,
 	bail,
-	function::{builtin, FuncVal},
+	function::{FuncVal, builtin},
 	parser::Source,
-	ContextBuilder, ContextInitializer as ContextInitializerT, ObjValueBuilder, Result, Thunk, Val,
 };
 use jrsonnet_gcmodule::Trace;
 
modifiedtests/tests/golden.rsdiffbeforeafterboth
--- a/tests/tests/golden.rs
+++ b/tests/tests/golden.rs
@@ -4,9 +4,9 @@
 };
 
 use jrsonnet_evaluator::{
+	FileImportResolver, State,
 	manifest::JsonFormat,
 	trace::{CompactFormat, PathResolver, TraceFormat},
-	FileImportResolver, State,
 };
 use jrsonnet_stdlib::ContextInitializer;
 mod common;
modifiedtests/tests/sanity.rsdiffbeforeafterboth
--- a/tests/tests/sanity.rs
+++ b/tests/tests/sanity.rs
@@ -1,7 +1,6 @@
 use jrsonnet_evaluator::{
-	bail,
+	FileImportResolver, Result, State, Val, bail,
 	trace::{CompactFormat, PathResolver, TraceFormat},
-	FileImportResolver, Result, State, Val,
 };
 use jrsonnet_stdlib::ContextInitializer;
 
modifiedtests/tests/std_native.rsdiffbeforeafterboth
--- a/tests/tests/std_native.rs
+++ b/tests/tests/std_native.rs
@@ -1,4 +1,4 @@
-use jrsonnet_evaluator::{function::builtin, trace::PathResolver, State};
+use jrsonnet_evaluator::{State, function::builtin, trace::PathResolver};
 use jrsonnet_stdlib::ContextInitializer;
 
 #[builtin]
@@ -14,9 +14,11 @@
 	state.context_initializer(std);
 	let state = state.build();
 
-	assert!(state
-		.evaluate_snippet("test", "std.native('example')(1, 3) == 4")
-		.unwrap()
-		.as_bool()
-		.expect("boolean output"));
+	assert!(
+		state
+			.evaluate_snippet("test", "std.native('example')(1, 3) == 4")
+			.unwrap()
+			.as_bool()
+			.expect("boolean output")
+	);
 }
modifiedtests/tests/suite.rsdiffbeforeafterboth
--- a/tests/tests/suite.rs
+++ b/tests/tests/suite.rs
@@ -4,8 +4,8 @@
 };
 
 use jrsonnet_evaluator::{
+	FileImportResolver, State, Val,
 	trace::{CompactFormat, PathResolver, TraceFormat},
-	FileImportResolver, State, Val,
 };
 use jrsonnet_stdlib::ContextInitializer;
 
modifiedtests/tests/typed_obj.rsdiffbeforeafterboth
--- a/tests/tests/typed_obj.rs
+++ b/tests/tests/typed_obj.rs
@@ -2,7 +2,7 @@
 
 use std::fmt::Debug;
 
-use jrsonnet_evaluator::{trace::PathResolver, typed::Typed, Result, State};
+use jrsonnet_evaluator::{Result, State, trace::PathResolver, typed::Typed};
 use jrsonnet_stdlib::ContextInitializer;
 
 #[derive(Clone, Typed, PartialEq, Debug)]