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
4 collections::hash_map::Entry,4 collections::hash_map::Entry,
5 fmt::{self, Debug},5 fmt::{self, Debug},
6 hash::{Hash, Hasher},6 hash::{Hash, Hasher},
7 mem,
8 ops::ControlFlow,
7};9};
810
9use educe::Educe;11use educe::Educe;
10use jrsonnet_gcmodule::{cc_dyn, Cc, Trace, Weak};12use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};
11use jrsonnet_interner::IStr;13use jrsonnet_interner::IStr;
12use jrsonnet_parser::{Span, Visibility};14use jrsonnet_parser::{Span, Visibility};
13use rustc_hash::{FxHashMap, FxHashSet};15use rustc_hash::{FxHashMap, FxHashSet};
74 pub struct SuperDepth(u32);76 pub struct SuperDepth(u32);
75 impl SuperDepth {77 impl SuperDepth {
76 pub(super) fn deepen(&mut self) {78 pub(super) fn deepen(&mut self) {
77 *self.0 += 179 self.0 += 1
78 }80 }
79 }81 }
8082
151}153}
152154
153#[allow(clippy::module_name_repetitions)]155#[allow(clippy::module_name_repetitions)]
154#[derive(Trace)]156#[derive(Trace, Default)]
155#[trace(tracking(force))]157#[trace(tracking(force))]
156pub struct OopObject {158pub struct OopObject {
157 // this: Option<ObjValue>,159 assertions: Vec<CcObjectAssertion>,
158 assertions: Cc<Vec<CcObjectAssertion>>,
159 this_entries: Cc<FxHashMap<IStr, ObjMember>>,160 this_entries: FxHashMap<IStr, ObjMember>,
160 value_cache: RefCell<FxHashMap<(IStr, Option<WeakObjValue>), CacheValue>>,
161}161}
162impl Debug for OopObject {162impl Debug for OopObject {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 f.debug_struct("OopObject")164 f.debug_struct("OopObject")
165 // .field("assertions", &self.assertions)
166 // .field("assertions_ran", &self.assertions_ran)
167 .field("this_entries", &self.this_entries)165 .field("this_entries", &self.this_entries)
168 // .field("value_cache", &self.value_cache)
169 .finish_non_exhaustive()166 .finish_non_exhaustive()
170 }167 }
171}168}
169impl OopObject {
170 fn is_empty(&self) -> bool {
171 self.assertions.is_empty() && self.this_entries.is_empty()
172 }
173}
172174
173type EnumFieldsHandler<'a> = dyn FnMut(SuperDepth, FieldIndex, IStr, Visibility) -> bool + 'a;175type EnumFieldsHandler<'a> =
176 dyn FnMut(SuperDepth, FieldIndex, IStr, EnumFields) -> ControlFlow<()> + 'a;
174177
178pub enum EnumFields {
179 Normal(Visibility),
180 Omit,
181}
182
175#[derive(Trace, Clone)]183#[derive(Trace, Clone)]
176pub enum ValueProcess {184pub enum GetFor {
177 None,185 // Return value
186 Final(Val),
178 SuperPlus,187 // Continue iterating over cores, add current value to sum stack
188 SuperPlus(Val),
189 // Ignore the field value, stop at this layer instead
190 Omit,
191 NotFound,
179}192}
180193
194#[derive(Acyclic, Clone)]
195pub enum FieldVisibility {
196 Found(Visibility),
197 Omit,
198 NotFound,
199}
200
201#[derive(Acyclic, Clone)]
202pub enum HasFieldIncludeHidden {
203 Exists,
204 NotFound,
205 Omit,
206}
207
181pub trait ObjectCore: Trace + Any + Debug {208pub trait ObjectCore: Trace + Any + Debug {
182 // If callback returns false, iteration stops, and this call returns false.209 // If callback returns false, iteration stops, and this call returns false.
183 fn enum_fields_core(210 fn enum_fields_core(
186 handler: &mut EnumFieldsHandler<'_>,213 handler: &mut EnumFieldsHandler<'_>,
187 ) -> bool;214 ) -> bool;
188215
189 fn has_field_include_hidden(&self, name: IStr) -> bool;216 fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden;
190217
191 fn get_for(&self, key: IStr, sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>>;218 fn get_for_core(&self, key: IStr, sup_this: SupThis) -> Result<GetFor>;
192 // fn get_for_uncached(&self, key: IStr, this: ObjValue) -> Result<Option<(Val, ValueProcess)>>;219 fn field_visibility_core(&self, field: IStr) -> FieldVisibility;
193 fn field_visibility(&self, field: IStr) -> Option<Visibility>;
194220
195 fn run_assertions_raw(&self, sup_this: SupThis) -> Result<()>;221 fn run_assertions_core(&self, sup_this: SupThis) -> Result<()>;
196}222}
197223
198#[derive(Clone, Trace)]224#[derive(Clone, Trace)]
220246
221cc_dyn!(247cc_dyn!(
222 #[derive(Clone, Debug)]248 #[derive(Clone, Debug)]
223 ObjCore, ObjectCore,249 CcObjectCore, ObjectCore,
224 pub fn new() {...}250 pub fn new() {...}
225);251);
226#[derive(Trace, Educe)]252#[derive(Trace, Educe)]
227#[educe(Debug)]253#[educe(Debug)]
228struct ObjValueInner {254struct ObjValueInner {
229 cores: Vec<ObjCore>,255 cores: Vec<CcObjectCore>,
230 assertions_ran: Cell<bool>,256 assertions_ran: Cell<bool>,
231 value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,257 value_cache: RefCell<FxHashMap<(IStr, CoreIdx), CacheValue>>,
232}258}
251 });277 });
252}278}
253279
280thread_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}
287
254#[allow(clippy::module_name_repetitions)]288#[allow(clippy::module_name_repetitions)]
255#[derive(Clone, Trace, Debug, Educe)]289#[derive(Clone, Trace, Debug, Educe)]
256#[educe(PartialEq, Hash, Eq)]290#[educe(PartialEq, Hash, Eq)]
257pub struct ObjValue(291pub struct ObjValue(
258 #[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc<ObjValueInner>,292 #[educe(PartialEq(method(Cc::ptr_eq)), Hash(method(identity_hash)))] Cc<ObjValueInner>,
259);293);
260294
295impl 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() == 0
301 }
302}
303
261#[derive(Trace, Debug)]304#[derive(Trace, Debug)]
262struct StandaloneSuperCore {305struct StandaloneSuperCore {
263 sup: CoreIdx,306 sup: CoreIdx,
269 super_depth: &mut SuperDepth,312 super_depth: &mut SuperDepth,
270 handler: &mut EnumFieldsHandler<'_>,313 handler: &mut EnumFieldsHandler<'_>,
271 ) -> bool {314 ) -> bool {
272 self.this315 self.this.enum_fields_idx(super_depth, handler, self.sup)
273 .enum_fields_internal(super_depth, handler, self.sup)
274 }316 }
275317
276 fn has_field_include_hidden(&self, name: IStr) -> bool {318 fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {
277 self.this.has_field_include_hidden_idx(name, self.sup)319 if self.this.has_field_include_hidden_idx(name, self.sup) {
320 HasFieldIncludeHidden::Exists
321 } else {
322 HasFieldIncludeHidden::NotFound
323 }
278 }324 }
279325
280 fn get_for(&self, key: IStr, _sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>> {326 fn get_for_core(&self, key: IStr, _sup_this: SupThis) -> Result<GetFor> {
281 let v = self.this.get_idx(key, self.sup)?;327 let v = self.this.get_idx(key, self.sup)?;
282 Ok(v.map(|v| (v, ValueProcess::None)))328 Ok(v.map_or(GetFor::NotFound, |v| GetFor::Final(v)))
283 }329 }
284330
285 fn field_visibility(&self, field: IStr) -> Option<Visibility> {331 fn field_visibility_core(&self, field: IStr) -> FieldVisibility {
286 self.this.field_visibility_idx(field, self.sup)332 match self.this.field_visibility_idx(field, self.sup) {
333 Some(c) => FieldVisibility::Found(c),
334 None => FieldVisibility::NotFound,
335 }
287 }336 }
288337
289 fn run_assertions_raw(&self, _sup_this: SupThis) -> Result<()> {338 fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {
290 self.this.run_assertions()339 self.this.run_assertions()
291 }340 }
292}341}
293342
294#[derive(Debug, Trace)]343#[derive(Debug, Acyclic)]
295struct EmptyObject;344struct OmitFieldsCore {
345 omit: FxHashSet<IStr>,
346}
296impl ObjectCore for EmptyObject {347impl ObjectCore for OmitFieldsCore {
297 fn enum_fields_core(348 fn enum_fields_core(
298 &self,349 &self,
299 _super_depth: &mut SuperDepth,350 super_depth: &mut SuperDepth,
300 _handler: &mut EnumFieldsHandler<'_>,351 handler: &mut EnumFieldsHandler<'_>,
301 ) -> bool {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 }
302 true360 true
303 }361 }
304362
305 fn has_field_include_hidden(&self, _name: IStr) -> bool {363 fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {
306 false364 if self.omit.contains(&name) {
365 return HasFieldIncludeHidden::Omit;
366 }
367 HasFieldIncludeHidden::NotFound
307 }368 }
308369
309 fn get_for(&self, _key: IStr, _sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>> {370 fn get_for_core(&self, key: IStr, _sup_this: SupThis) -> Result<GetFor> {
371 if self.omit.contains(&key) {
310 Ok(None)372 return Ok(GetFor::Omit);
373 }
374 Ok(GetFor::NotFound)
311 }375 }
312376
313 fn run_assertions_raw(&self, _sup_this: SupThis) -> Result<()> {377 fn field_visibility_core(&self, field: IStr) -> FieldVisibility {
378 if self.omit.contains(&field) {
314 Ok(())379 return FieldVisibility::Omit;
380 }
381 FieldVisibility::NotFound
315 }382 }
316383
317 fn field_visibility(&self, _field: IStr) -> Option<Visibility> {384 fn run_assertions_core(&self, _sup_this: SupThis) -> Result<()> {
318 None385 Ok(())
319 }386 }
320}387}
321388
363 if !self.sup.super_exists() {430 if !self.sup.super_exists() {
364 bail!(NoSuperFound)431 bail!(NoSuperFound)
365 }432 }
366 Ok(ObjValue::new(StandaloneSuperCore {433 let mut out = ObjValue::builder();
434 out.reserve_cores(1).extend_with_core(StandaloneSuperCore {
367 sup: self.sup,435 sup: self.sup,
368 this: self.this.clone(),436 this: self.this.clone(),
369 }))437 });
438 Ok(out.build())
370 }439 }
371 pub fn this(&self) -> &ObjValue {440 pub fn this(&self) -> &ObjValue {
372 &self.this441 &self.this
385}454}
386455
387impl ObjValue {456impl 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 {457 pub fn builder() -> ObjValueBuilder {
399 ObjValueBuilder::new()458 ObjValueBuilder::new()
400 }459 }
420 ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())479 ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default())
421 }480 }
422481
482 pub fn extend(&mut self) -> ObjValueBuilder {
483 let mut out = ObjValueBuilder::new();
484 out.with_super(self.clone());
485 out
486 }
487
423 #[must_use]488 #[must_use]
424 pub fn extend_from(&self, sup: Self) -> Self {489 pub fn extend_from(&self, sup: Self) -> Self {
425 let mut cores = sup.0.cores.clone();490 let mut cores = sup.0.cores.clone();
442 .filter(|(_, (visible, _))| *visible)507 .filter(|(_, (visible, _))| *visible)
443 .count()508 .count()
444 }509 }
445 pub fn is_empty(&self) -> bool {
446 self.len() == 0
447 }
448 /// For each field, calls callback.510 /// For each field, calls callback.
449 /// If callback returns false - ends iteration prematurely.511 /// If callback returns false - ends iteration prematurely.
450 ///512 ///
451 /// Returns false if ended prematurely513 /// Returns false if ended prematurely
452 pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool {514 pub fn enum_fields(&self, handler: &mut EnumFieldsHandler<'_>) -> bool {
453 let mut super_depth = SuperDepth::default();515 let mut super_depth = SuperDepth::default();
454 self.enum_fields_internal(516 self.enum_fields_idx(
455 &mut super_depth,517 &mut super_depth,
456 handler,518 handler,
457 CoreIdx {519 CoreIdx {
458 idx: self.0.cores.len(),520 idx: self.0.cores.len(),
459 },521 },
460 )522 )
461 }523 }
462 fn enum_fields_internal(524 fn enum_fields_idx(
463 &self,525 &self,
464 super_depth: &mut SuperDepth,526 super_depth: &mut SuperDepth,
465 handler: &mut EnumFieldsHandler<'_>,527 handler: &mut EnumFieldsHandler<'_>,
483 )545 )
484 }546 }
485 fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {547 fn has_field_include_hidden_idx(&self, name: IStr, core: CoreIdx) -> bool {
486 self.0.cores[..core.idx]548 for ele in self.0.cores[..core.idx].iter().rev() {
487 .iter()
488 .rev()
489 .any(|v| v.0.has_field_include_hidden(name.clone()))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 false
490 }556 }
491 pub fn has_field(&self, name: IStr) -> bool {557 pub fn has_field(&self, name: IStr) -> bool {
492 match self.field_visibility(name) {558 match self.field_visibility(name) {
544 sup: CoreIdx { idx: sup },610 sup: CoreIdx { idx: sup },
545 this: self.clone(),611 this: self.clone(),
546 };612 };
547 if let Some((val, proc)) = core.0.get_for(key.clone(), sup_this)? {613 match core.0.get_for_core(key.clone(), sup_this)? {
548 match proc {614 GetFor::Final(val) if add_stack.is_empty() => return Ok(Some(val)),
549 ValueProcess::None if add_stack.is_empty() => return Ok(Some(val)),
550 ValueProcess::None => {615 GetFor::Final(val) => {
551 add_stack.push(val);
552 break;
553 }
554 ValueProcess::SuperPlus => {
555 add_stack.push(val);616 add_stack.push(val);
556 }617 break;
557 }618 }
619 GetFor::SuperPlus(val) => {
620 add_stack.push(val);
621 }
622 GetFor::Omit => {
623 break;
624 }
625 GetFor::NotFound => {
626 continue;
627 }
558 }628 }
559 }629 }
560 if add_stack.is_empty() {630 if add_stack.is_empty() {
594 fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {664 fn field_visibility_idx(&self, field: IStr, core: CoreIdx) -> Option<Visibility> {
595 let mut exists = false;665 let mut exists = false;
596 for ele in self.0.cores[..core.idx].iter().rev() {666 for ele in self.0.cores[..core.idx].iter().rev() {
597 let vis = ele.0.field_visibility(field.clone());667 let vis = ele.0.field_visibility_core(field.clone());
598 match vis {668 match vis {
599 Some(Visibility::Unhide | Visibility::Hidden) => return vis,669 FieldVisibility::Found(vis @ (Visibility::Unhide | Visibility::Hidden)) => {
670 return Some(vis)
600 Some(Visibility::Normal) => exists = true,671 }
672 FieldVisibility::Found(Visibility::Normal) => exists = true,
601 None => {}673 FieldVisibility::NotFound => {}
674 FieldVisibility::Omit => break,
602 }675 }
603 }676 }
604 exists.then_some(Visibility::Normal)677 exists.then_some(Visibility::Normal)
616 sup: CoreIdx { idx },689 sup: CoreIdx { idx },
617 this: self.clone(),690 this: self.clone(),
618 };691 };
619 ele.0.run_assertions_raw(sup_this).inspect_err(|_e| {692 ele.0.run_assertions_core(sup_this).inspect_err(|_e| {
620 finish_asserting(self);693 finish_asserting(self);
621 })?;694 })?;
622 }695 }
664 self.enum_fields(&mut |depth, index, name, visibility| {737 self.enum_fields(&mut |depth, index, name, visibility| {
665 let new_sort_key = FieldSortKey::new(depth, index);738 let new_sort_key = FieldSortKey::new(depth, index);
666 let entry = out.entry(name);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 }
667 let (visible, _) = entry.or_insert((true, new_sort_key));746 let (visible, _) = entry.or_insert((true, new_sort_key));
668 match visibility {747 match visibility {
669 Visibility::Normal => {}748 EnumFields::Omit => unreachable!(),
749 EnumFields::Normal(Visibility::Normal) => {}
670 Visibility::Hidden => {750 EnumFields::Normal(Visibility::Hidden) => {
671 *visible = false;751 *visible = false;
672 }752 }
673 Visibility::Unhide => {753 EnumFields::Normal(Visibility::Unhide) => {
674 *visible = true;754 *visible = true;
675 }755 }
676 };756 };
677 false757 return ControlFlow::Continue(());
678 });758 });
679 out759 out
680 }760 }
776856
777impl OopObject {857impl OopObject {
778 pub fn new(858 pub fn new(
779 this_entries: Cc<FxHashMap<IStr, ObjMember>>,859 this_entries: FxHashMap<IStr, ObjMember>,
780 assertions: Cc<Vec<CcObjectAssertion>>,860 assertions: Vec<CcObjectAssertion>,
781 ) -> Self {861 ) -> Self {
782 Self {862 Self {
783 this_entries,863 this_entries,
784 value_cache: RefCell::new(FxHashMap::new()),
785 assertions,864 assertions,
786 }865 }
787 }866 }
794 handler: &mut EnumFieldsHandler<'_>,873 handler: &mut EnumFieldsHandler<'_>,
795 ) -> bool {874 ) -> bool {
796 for (name, member) in self.this_entries.iter() {875 for (name, member) in self.this_entries.iter() {
797 if handler(876 if matches!(
877 handler(
798 *super_depth,878 *super_depth,
799 member.original_index,879 member.original_index,
800 name.clone(),880 name.clone(),
801 member.flags.visibility(),881 EnumFields::Normal(member.flags.visibility()),
882 ),
883 ControlFlow::Break(())
802 ) {884 ) {
803 return false;885 return false;
804 }886 }
805 }887 }
806 true888 true
807 }889 }
808890
809 fn has_field_include_hidden(&self, name: IStr) -> bool {891 fn has_field_include_hidden_core(&self, name: IStr) -> HasFieldIncludeHidden {
810 self.this_entries.contains_key(&name)892 if self.this_entries.contains_key(&name) {
893 HasFieldIncludeHidden::Exists
894 } else {
895 HasFieldIncludeHidden::NotFound
896 }
811 }897 }
812898
813 fn get_for(&self, key: IStr, sup_this: SupThis) -> Result<Option<(Val, ValueProcess)>> {899 fn get_for_core(&self, key: IStr, sup_this: SupThis) -> Result<GetFor> {
814 match self.this_entries.get(&key) {900 match self.this_entries.get(&key) {
815 Some(k) => Ok(Some((901 Some(k) => {
816 k.invoke.evaluate(sup_this)?,902 let v = k.invoke.evaluate(sup_this)?;
817 if k.flags.add() {903 Ok(if k.flags.add() {
818 ValueProcess::SuperPlus904 GetFor::SuperPlus(v)
819 } else {905 } else {
820 ValueProcess::None906 GetFor::Final(v)
821 },907 })
822 ))),908 }
823 None => Ok(None),909 None => Ok(GetFor::NotFound),
824 }910 }
825 }911 }
826 fn field_visibility(&self, name: IStr) -> Option<Visibility> {912 fn field_visibility_core(&self, name: IStr) -> FieldVisibility {
827 Some(self.this_entries.get(&name)?.flags.visibility())913 match self.this_entries.get(&name) {
914 Some(f) => FieldVisibility::Found(f.flags.visibility()),
915 None => FieldVisibility::NotFound,
916 }
828 }917 }
829918
830 fn run_assertions_raw(&self, sup_this: SupThis) -> Result<()> {919 fn run_assertions_core(&self, sup_this: SupThis) -> Result<()> {
831 if self.assertions.is_empty() {920 if self.assertions.is_empty() {
832 return Ok(());921 return Ok(());
833 }922 }
840929
841#[allow(clippy::module_name_repetitions)]930#[allow(clippy::module_name_repetitions)]
842pub struct ObjValueBuilder {931pub struct ObjValueBuilder {
843 sup: Option<ObjValue>,932 sup: Vec<CcObjectCore>,
844 map: FxHashMap<IStr, ObjMember>,933
845 assertions: Vec<CcObjectAssertion>,934 new: OopObject,
846 next_field_index: FieldIndex,935 next_field_index: FieldIndex,
847}936}
848impl ObjValueBuilder {937impl ObjValueBuilder {
851 }940 }
852 pub fn with_capacity(capacity: usize) -> Self {941 pub fn with_capacity(capacity: usize) -> Self {
853 Self {942 Self {
854 sup: None,943 sup: vec![],
855 map: FxHashMap::with_capacity(capacity),944 new: OopObject {
945 assertions: vec![],
946 this_entries: FxHashMap::with_capacity(capacity),
856 assertions: Vec::new(),947 },
857 next_field_index: FieldIndex::default(),948 next_field_index: FieldIndex::default(),
858 }949 }
859 }950 }
951 pub fn reserve_cores(&mut self, capacity: usize) -> &mut Self {
952 self.sup.reserve_exact(capacity);
953 self
954 }
860 pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {955 pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self {
861 self.assertions.reserve_exact(capacity);956 self.new.assertions.reserve_exact(capacity);
862 self957 self
863 }958 }
864 pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {959 pub fn with_super(&mut self, super_obj: ObjValue) -> &mut Self {
865 self.sup = Some(super_obj);960 self.sup = super_obj.0.cores.clone();
866 self961 self
867 }962 }
868963
869 pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {964 pub fn assert(&mut self, assertion: impl ObjectAssertion + 'static) -> &mut Self {
870 self.assertions.push(CcObjectAssertion::new(assertion));965 self.new.assertions.push(CcObjectAssertion::new(assertion));
871 self966 self
872 }967 }
873 pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {968 pub fn field(&mut self, name: impl Into<IStr>) -> ObjMemberBuilder<ValueBuilder<'_>> {
892 Ok(self)987 Ok(self)
893 }988 }
894989
895 pub fn build(self) -> ObjValue {990 pub fn extend_with_core(&mut self, core: impl ObjectCore) {
896 if self.sup.is_none() && self.map.is_empty() && self.assertions.is_empty() {991 self.commit();
992 self.sup.push(CcObjectCore::new(core));
993 }
994
995 fn commit(&mut self) {
996 if !self.new.is_empty() {
897 return ObjValue::new_empty();997 self.sup.push(CcObjectCore::new(mem::take(&mut self.new)));
898 }998 }
899 let res = ObjValue::new(OopObject::new(Cc::new(self.map), Cc::new(self.assertions)));999 self.next_field_index = FieldIndex::default();
1000 }
1001
1002 pub fn with_fields_omitted(&mut self, omit: FxHashSet<IStr>) {
1003 self.commit();
1004 self.sup.push(CcObjectCore::new(OmitFieldsCore { omit }));
1005 }
1006
1007 pub fn build(mut self) -> ObjValue {
1008 self.commit();
900 self.sup.map(|sup| res.extend_from(sup)).unwrap_or(res)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 }))
901 }1017 }
902}1018}
903impl Default for ObjValueBuilder {1019impl Default for ObjValueBuilder {
968 pub fn value(self, value: impl Into<Val>) {1084 pub fn value(self, value: impl Into<Val>) {
969 let (receiver, name, member) =1085 let (receiver, name, member) =
970 self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));1086 self.build_member(MaybeUnbound::Bound(Thunk::evaluated(value.into())));
971 let entry = receiver.0.map.entry(name);1087 let entry = receiver.0.new.this_entries.entry(name);
972 entry.insert_entry(member);1088 entry.insert_entry(member);
973 }1089 }
9741090
985 pub fn binding(self, binding: MaybeUnbound) -> Result<()> {1101 pub fn binding(self, binding: MaybeUnbound) -> Result<()> {
986 let (receiver, name, member) = self.build_member(binding);1102 let (receiver, name, member) = self.build_member(binding);
987 let location = member.location.clone();1103 let location = member.location.clone();
988 let old = receiver.0.map.insert(name.clone(), member);1104 let old = receiver.0.new.this_entries.insert(name.clone(), member);
989 if old.is_some() {1105 if old.is_some() {
990 in_frame(1106 in_frame(
991 CallLocation(location.as_ref()),1107 CallLocation(location.as_ref()),
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)]