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

difftreelog

perf move std.object[Keys]Values[All] to native

Yaroslav Bolyukin2023-08-10parent: #be6e85a.patch.diff
in: master

6 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/arr/spec.rs
1use std::{any::Any, cell::RefCell, fmt::Debug, iter, mem::replace};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::{IBytes, IStr};5use jrsonnet_parser::LocExpr;67use super::ArrValue;8use crate::{9	error::ErrorKind::InfiniteRecursionDetected,10	evaluate,11	function::FuncVal,12	val::{StrValue, ThunkValue},13	Context, Error, Result, Thunk, Val,14};1516pub trait ArrayLike: Any + Trace + Debug {17	fn len(&self) -> usize;18	fn is_empty(&self) -> bool {19		self.len() == 020	}21	fn get(&self, index: usize) -> Result<Option<Val>>;22	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;23	fn get_cheap(&self, index: usize) -> Option<Val>;2425	fn is_cheap(&self) -> bool;26}2728#[derive(Debug, Trace)]29pub struct SliceArray {30	pub(crate) inner: ArrValue,31	pub(crate) from: u32,32	pub(crate) to: u32,33	pub(crate) step: u32,34}3536impl SliceArray {37	fn iter(&self) -> impl Iterator<Item = Result<Val>> + '_ {38		self.inner39			.iter()40			.skip(self.from as usize)41			.take((self.to - self.from) as usize)42			.step_by(self.step as usize)43	}4445	fn iter_lazy(&self) -> impl Iterator<Item = Thunk<Val>> + '_ {46		self.inner47			.iter_lazy()48			.skip(self.from as usize)49			.take((self.to - self.from) as usize)50			.step_by(self.step as usize)51	}5253	fn iter_cheap(&self) -> Option<impl crate::arr::ArrayLikeIter<Val> + '_> {54		Some(55			self.inner56				.iter_cheap()?57				.skip(self.from as usize)58				.take((self.to - self.from) as usize)59				.step_by(self.step as usize),60		)61	}62}63impl ArrayLike for SliceArray {64	fn len(&self) -> usize {65		iter::repeat(())66			.take((self.to - self.from) as usize)67			.step_by(self.step as usize)68			.count()69	}7071	fn get(&self, index: usize) -> Result<Option<Val>> {72		self.iter().nth(index).transpose()73	}7475	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {76		self.iter_lazy().nth(index)77	}7879	fn get_cheap(&self, index: usize) -> Option<Val> {80		self.iter_cheap()?.nth(index)81	}82	fn is_cheap(&self) -> bool {83		self.inner.is_cheap()84	}85}8687#[derive(Trace, Debug)]88pub struct CharArray(pub Vec<char>);89impl ArrayLike for CharArray {90	fn len(&self) -> usize {91		self.0.len()92	}9394	fn get(&self, index: usize) -> Result<Option<Val>> {95		Ok(self.get_cheap(index))96	}9798	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {99		self.get_cheap(index).map(Thunk::evaluated)100	}101102	fn get_cheap(&self, index: usize) -> Option<Val> {103		self.0104			.get(index)105			.map(|v| Val::Str(StrValue::Flat(IStr::from(*v))))106	}107	fn is_cheap(&self) -> bool {108		true109	}110}111112#[derive(Trace, Debug)]113pub struct BytesArray(pub IBytes);114impl ArrayLike for BytesArray {115	fn len(&self) -> usize {116		self.0.len()117	}118119	fn get(&self, index: usize) -> Result<Option<Val>> {120		Ok(self.get_cheap(index))121	}122123	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {124		self.get_cheap(index).map(Thunk::evaluated)125	}126127	fn get_cheap(&self, index: usize) -> Option<Val> {128		self.0.get(index).map(|v| Val::Num(f64::from(*v)))129	}130	fn is_cheap(&self) -> bool {131		true132	}133}134135#[derive(Debug, Trace, Clone)]136enum ArrayThunk<T: 'static + Trace> {137	Computed(Val),138	Errored(Error),139	Waiting(T),140	Pending,141}142143#[derive(Debug, Trace, Clone)]144pub struct ExprArray {145	ctx: Context,146	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,147}148impl ExprArray {149	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {150		Self {151			ctx,152			cached: Cc::new(RefCell::new(153				items.into_iter().map(ArrayThunk::Waiting).collect(),154			)),155		}156	}157}158impl ArrayLike for ExprArray {159	fn len(&self) -> usize {160		self.cached.borrow().len()161	}162	fn get(&self, index: usize) -> Result<Option<Val>> {163		if index >= self.len() {164			return Ok(None);165		}166		match &self.cached.borrow()[index] {167			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),168			ArrayThunk::Errored(e) => return Err(e.clone()),169			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),170			ArrayThunk::Waiting(..) => {}171		};172173		let ArrayThunk::Waiting(expr) =174			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)175		else {176			unreachable!()177		};178179		let new_value = match evaluate(self.ctx.clone(), &expr) {180			Ok(v) => v,181			Err(e) => {182				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());183				return Err(e);184			}185		};186		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());187		Ok(Some(new_value))188	}189	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {190		#[derive(Trace)]191		struct ArrayElement {192			arr_thunk: ExprArray,193			index: usize,194		}195196		impl ThunkValue for ArrayElement {197			type Output = Val;198199			fn get(self: Box<Self>) -> Result<Self::Output> {200				self.arr_thunk201					.get(self.index)202					.transpose()203					.expect("index checked")204			}205		}206207		if index >= self.len() {208			return None;209		}210		match &self.cached.borrow()[index] {211			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),212			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),213			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}214		};215216		Some(Thunk::new(ArrayElement {217			arr_thunk: self.clone(),218			index,219		}))220	}221	fn get_cheap(&self, _index: usize) -> Option<Val> {222		None223	}224	fn is_cheap(&self) -> bool {225		false226	}227}228229#[derive(Trace, Debug)]230pub struct ExtendedArray {231	pub a: ArrValue,232	pub b: ArrValue,233	split: usize,234	len: usize,235}236impl ExtendedArray {237	pub fn new(a: ArrValue, b: ArrValue) -> Self {238		let a_len = a.len();239		let b_len = b.len();240		Self {241			a,242			b,243			split: a_len,244			len: a_len.checked_add(b_len).expect("too large array value"),245		}246	}247}248249struct WithExactSize<I>(I, usize);250impl<I, T> Iterator for WithExactSize<I>251where252	I: Iterator<Item = T>,253{254	type Item = T;255256	fn next(&mut self) -> Option<Self::Item> {257		self.0.next()258	}259	fn nth(&mut self, n: usize) -> Option<Self::Item> {260		self.0.nth(n)261	}262	fn size_hint(&self) -> (usize, Option<usize>) {263		(self.1, Some(self.1))264	}265}266impl<I> DoubleEndedIterator for WithExactSize<I>267where268	I: DoubleEndedIterator,269{270	fn next_back(&mut self) -> Option<Self::Item> {271		self.0.next_back()272	}273	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {274		self.0.nth_back(n)275	}276}277impl<I> ExactSizeIterator for WithExactSize<I>278where279	I: Iterator,280{281	fn len(&self) -> usize {282		self.1283	}284}285impl ArrayLike for ExtendedArray {286	fn get(&self, index: usize) -> Result<Option<Val>> {287		if self.split > index {288			self.a.get(index)289		} else {290			self.b.get(index - self.split)291		}292	}293	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {294		if self.split > index {295			self.a.get_lazy(index)296		} else {297			self.b.get_lazy(index - self.split)298		}299	}300301	fn len(&self) -> usize {302		self.len303	}304305	fn get_cheap(&self, index: usize) -> Option<Val> {306		if self.split > index {307			self.a.get_cheap(index)308		} else {309			self.b.get_cheap(index - self.split)310		}311	}312	fn is_cheap(&self) -> bool {313		self.a.is_cheap() && self.b.is_cheap()314	}315}316317#[derive(Trace, Debug)]318pub struct LazyArray(pub Vec<Thunk<Val>>);319impl ArrayLike for LazyArray {320	fn len(&self) -> usize {321		self.0.len()322	}323	fn get(&self, index: usize) -> Result<Option<Val>> {324		let Some(v) = self.0.get(index) else {325			return Ok(None);326		};327		v.evaluate().map(Some)328	}329	fn get_cheap(&self, _index: usize) -> Option<Val> {330		None331	}332	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {333		self.0.get(index).cloned()334	}335	fn is_cheap(&self) -> bool {336		false337	}338}339340#[derive(Trace, Debug)]341pub struct EagerArray(pub Vec<Val>);342impl ArrayLike for EagerArray {343	fn len(&self) -> usize {344		self.0.len()345	}346347	fn get(&self, index: usize) -> Result<Option<Val>> {348		Ok(self.0.get(index).cloned())349	}350351	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {352		self.0.get(index).cloned().map(Thunk::evaluated)353	}354355	fn get_cheap(&self, index: usize) -> Option<Val> {356		self.0.get(index).cloned()357	}358	fn is_cheap(&self) -> bool {359		true360	}361}362363/// Inclusive range type364#[derive(Debug, Trace, PartialEq, Eq)]365pub struct RangeArray {366	start: i32,367	end: i32,368}369impl RangeArray {370	pub fn empty() -> Self {371		Self::new_exclusive(0, 0)372	}373	pub fn new_exclusive(start: i32, end: i32) -> Self {374		end.checked_sub(1)375			.map_or_else(Self::empty, |end| Self { start, end })376	}377	pub fn new_inclusive(start: i32, end: i32) -> Self {378		Self { start, end }379	}380	fn range(&self) -> impl Iterator<Item = i32> + ExactSizeIterator + DoubleEndedIterator {381		WithExactSize(382			self.start..=self.end,383			(self.end as usize)384				.wrapping_sub(self.start as usize)385				.wrapping_add(1),386		)387	}388}389390impl ArrayLike for RangeArray {391	fn len(&self) -> usize {392		self.range().len()393	}394	fn is_empty(&self) -> bool {395		self.range().len() == 0396	}397398	fn get(&self, index: usize) -> Result<Option<Val>> {399		Ok(self.get_cheap(index))400	}401402	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {403		self.get_cheap(index).map(Thunk::evaluated)404	}405406	fn get_cheap(&self, index: usize) -> Option<Val> {407		self.range().nth(index).map(|i| Val::Num(f64::from(i)))408	}409	fn is_cheap(&self) -> bool {410		true411	}412}413414#[derive(Debug, Trace)]415pub struct ReverseArray(pub ArrValue);416impl ArrayLike for ReverseArray {417	fn len(&self) -> usize {418		self.0.len()419	}420421	fn get(&self, index: usize) -> Result<Option<Val>> {422		self.0.get(self.0.len() - index - 1)423	}424425	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {426		self.0.get_lazy(self.0.len() - index - 1)427	}428429	fn get_cheap(&self, index: usize) -> Option<Val> {430		self.0.get_cheap(self.0.len() - index - 1)431	}432	fn is_cheap(&self) -> bool {433		self.0.is_cheap()434	}435}436437#[derive(Trace, Debug, Clone)]438pub struct MappedArray {439	inner: ArrValue,440	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,441	mapper: FuncVal,442}443impl MappedArray {444	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {445		let len = inner.len();446		Self {447			inner,448			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),449			mapper,450		}451	}452}453impl ArrayLike for MappedArray {454	fn len(&self) -> usize {455		self.cached.borrow().len()456	}457458	fn get(&self, index: usize) -> Result<Option<Val>> {459		if index >= self.len() {460			return Ok(None);461		}462		match &self.cached.borrow()[index] {463			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),464			ArrayThunk::Errored(e) => return Err(e.clone()),465			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),466			ArrayThunk::Waiting(..) => {}467		};468469		let ArrayThunk::Waiting(_) =470			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)471		else {472			unreachable!()473		};474475		let val = self476			.inner477			.get(index)478			.transpose()479			.expect("index checked")480			.and_then(|r| self.mapper.evaluate_simple(&(r,), false));481482		let new_value = match val {483			Ok(v) => v,484			Err(e) => {485				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());486				return Err(e);487			}488		};489		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());490		Ok(Some(new_value))491	}492	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {493		#[derive(Trace)]494		struct ArrayElement {495			arr_thunk: MappedArray,496			index: usize,497		}498499		impl ThunkValue for ArrayElement {500			type Output = Val;501502			fn get(self: Box<Self>) -> Result<Self::Output> {503				self.arr_thunk504					.get(self.index)505					.transpose()506					.expect("index checked")507			}508		}509510		if index >= self.len() {511			return None;512		}513		match &self.cached.borrow()[index] {514			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),515			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),516			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}517		};518519		Some(Thunk::new(ArrayElement {520			arr_thunk: self.clone(),521			index,522		}))523	}524525	fn get_cheap(&self, _index: usize) -> Option<Val> {526		None527	}528	fn is_cheap(&self) -> bool {529		false530	}531}532533#[derive(Trace, Debug)]534pub struct RepeatedArray {535	data: ArrValue,536	repeats: usize,537	total_len: usize,538}539impl RepeatedArray {540	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {541		let total_len = data.len().checked_mul(repeats)?;542		Some(Self {543			data,544			repeats,545			total_len,546		})547	}548}549550impl ArrayLike for RepeatedArray {551	fn len(&self) -> usize {552		self.total_len553	}554555	fn get(&self, index: usize) -> Result<Option<Val>> {556		if index > self.total_len {557			return Ok(None);558		}559		self.data.get(index % self.data.len())560	}561562	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {563		if index > self.total_len {564			return None;565		}566		self.data.get_lazy(index % self.data.len())567	}568569	fn get_cheap(&self, index: usize) -> Option<Val> {570		if index > self.total_len {571			return None;572		}573		self.data.get_cheap(index % self.data.len())574	}575	fn is_cheap(&self) -> bool {576		self.data.is_cheap()577	}578}
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -12,12 +12,13 @@
 use rustc_hash::FxHashMap;
 
 use crate::{
-	error::{Error, ErrorKind::*},
+	arr::{PickObjectKeyValues, PickObjectValues},
+	error::{suggest_object_fields, Error, ErrorKind::*},
 	function::CallLocation,
 	gc::{GcHashMap, GcHashSet, TraceBox},
 	operator::evaluate_add_op,
 	tb, throw,
-	val::ThunkValue,
+	val::{ArrValue, ThunkValue},
 	MaybeUnbound, Result, State, Thunk, Unbound, Val,
 };
 
@@ -398,6 +399,14 @@
 		self.0.get_for(key, this)
 	}
 
+	pub fn get_or_bail(&self, key: IStr) -> Result<Val> {
+		let Some(value) = self.get(key.clone())? else {
+			let suggestions = suggest_object_fields(self, key.clone());
+			throw!(NoSuchField(key, suggestions))
+		};
+		Ok(value)
+	}
+
 	fn get_raw(&self, key: IStr, this: ObjValue) -> Result<Option<Val>> {
 		self.0.get_for_uncached(key, this)
 	}
@@ -452,6 +461,25 @@
 			key,
 		}))
 	}
+	pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {
+		#[derive(Trace)]
+		struct ThunkGet {
+			obj: ObjValue,
+			key: IStr,
+		}
+		impl ThunkValue for ThunkGet {
+			type Output = Val;
+
+			fn get(self: Box<Self>) -> Result<Self::Output> {
+				Ok(self.obj.get_or_bail(self.key)?)
+			}
+		}
+
+		Thunk::new(ThunkGet {
+			obj: self.clone(),
+			key,
+		})
+	}
 	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
 		Cc::ptr_eq(&a.0, &b.0)
 	}
@@ -529,6 +557,51 @@
 			preserve_order,
 		)
 	}
+	pub fn values_ex(
+		&self,
+		include_hidden: bool,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> ArrValue {
+		ArrValue::new(PickObjectValues::new(
+			self.clone(),
+			self.fields_ex(
+				include_hidden,
+				#[cfg(feature = "exp-preserve-order")]
+				preserve_order,
+			),
+		))
+	}
+	pub fn values(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> ArrValue {
+		self.values_ex(
+			false,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		)
+	}
+	pub fn key_values_ex(
+		&self,
+		include_hidden: bool,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> ArrValue {
+		ArrValue::new(PickObjectKeyValues::new(
+			self.clone(),
+			self.fields_ex(
+				include_hidden,
+				#[cfg(feature = "exp-preserve-order")]
+				preserve_order,
+			),
+		))
+	}
+	pub fn key_values(
+		&self,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> ArrValue {
+		self.key_values_ex(
+			false,
+			#[cfg(feature = "exp-preserve-order")]
+			preserve_order,
+		)
+	}
 }
 
 impl OopObject {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -1,7 +1,6 @@
 use std::{
 	cell::RefCell,
 	fmt::{self, Debug, Display},
-	hash::Hasher,
 	mem::replace,
 	rc::Rc,
 };
@@ -9,7 +8,6 @@
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
 use jrsonnet_types::ValType;
-use rustc_hash::FxHasher;
 
 pub use crate::arr::{ArrValue, ArrayLike};
 use crate::{
@@ -50,6 +48,12 @@
 	pub fn errored(e: Error) -> Self {
 		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))
 	}
+	pub fn result(res: Result<T, Error>) -> Self {
+		match res {
+			Ok(o) => Self::evaluated(o),
+			Err(e) => Self::errored(e),
+		}
+	}
 }
 
 impl<T> Thunk<T>
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -138,6 +138,10 @@
 		("base64DecodeBytes", builtin_base64_decode_bytes::INST),
 		// Objects
 		("objectFieldsEx", builtin_object_fields_ex::INST),
+		("objectValues", builtin_object_values::INST),
+		("objectValuesAll", builtin_object_values_all::INST),
+		("objectKeysValues", builtin_object_keys_values::INST),
+		("objectKeysValuesAll", builtin_object_keys_values_all::INST),
 		("objectHasEx", builtin_object_has_ex::INST),
 		("objectRemoveKey", builtin_object_remove_key::INST),
 		// Manifest
modifiedcrates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/objects.rs
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -1,6 +1,6 @@
 use jrsonnet_evaluator::{
 	function::builtin,
-	val::{StrValue, Val},
+	val::{ArrValue, StrValue, Val},
 	IStr, ObjValue, ObjValueBuilder,
 };
 
@@ -23,6 +23,82 @@
 		.collect::<Vec<_>>()
 }
 
+pub fn builtin_object_values_ex(
+	o: ObjValue,
+	include_hidden: bool,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> ArrValue {
+	#[cfg(feature = "exp-preserve-order")]
+	let preserve_order = preserve_order.unwrap_or(false);
+	o.values_ex(
+		include_hidden,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+#[builtin]
+pub fn builtin_object_values(
+	o: ObjValue,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> ArrValue {
+	builtin_object_values_ex(
+		o,
+		false,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+#[builtin]
+pub fn builtin_object_values_all(
+	o: ObjValue,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> ArrValue {
+	builtin_object_values_ex(
+		o,
+		true,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+
+pub fn builtin_object_keys_values_ex(
+	o: ObjValue,
+	include_hidden: bool,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> ArrValue {
+	#[cfg(feature = "exp-preserve-order")]
+	let preserve_order = preserve_order.unwrap_or(false);
+	o.key_values_ex(
+		include_hidden,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+#[builtin]
+pub fn builtin_object_keys_values(
+	o: ObjValue,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> ArrValue {
+	builtin_object_keys_values_ex(
+		o,
+		false,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+#[builtin]
+pub fn builtin_object_keys_values_all(
+	o: ObjValue,
+	#[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> ArrValue {
+	builtin_object_keys_values_ex(
+		o,
+		true,
+		#[cfg(feature = "exp-preserve-order")]
+		preserve_order,
+	)
+}
+
 #[builtin]
 pub fn builtin_object_has_ex(obj: ObjValue, fname: IStr, hidden: bool) -> bool {
 	obj.has_field_ex(fname, hidden)
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -268,18 +268,6 @@
   objectHasAll(o, f)::
     std.objectHasEx(o, f, true),
 
-  objectValues(o)::
-    [o[k] for k in std.objectFields(o)],
-
-  objectValuesAll(o)::
-    [o[k] for k in std.objectFieldsAll(o)],
-
-  objectKeysValues(o)::
-    [{ key: k, value: o[k] } for k in std.objectFields(o)],
-	
-  objectKeysValuesAll(o)::
-		[{ key: k, value: o[k] } for k in std.objectFieldsAll(o)],
-
   resolvePath(f, r)::
     local arr = std.split(f, '/');
     std.join('/', std.makeArray(std.length(arr) - 1, function(i) arr[i]) + [r]),