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
after · 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	typed::Typed,13	val::{StrValue, ThunkValue},14	Context, Error, ObjValue, Result, Thunk, Val,15};1617pub trait ArrayLike: Any + Trace + Debug {18	fn len(&self) -> usize;19	fn is_empty(&self) -> bool {20		self.len() == 021	}22	fn get(&self, index: usize) -> Result<Option<Val>>;23	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;24	fn get_cheap(&self, index: usize) -> Option<Val>;2526	fn is_cheap(&self) -> bool;27}2829#[derive(Debug, Trace)]30pub struct SliceArray {31	pub(crate) inner: ArrValue,32	pub(crate) from: u32,33	pub(crate) to: u32,34	pub(crate) step: u32,35}3637impl SliceArray {38	fn iter(&self) -> impl Iterator<Item = Result<Val>> + '_ {39		self.inner40			.iter()41			.skip(self.from as usize)42			.take((self.to - self.from) as usize)43			.step_by(self.step as usize)44	}4546	fn iter_lazy(&self) -> impl Iterator<Item = Thunk<Val>> + '_ {47		self.inner48			.iter_lazy()49			.skip(self.from as usize)50			.take((self.to - self.from) as usize)51			.step_by(self.step as usize)52	}5354	fn iter_cheap(&self) -> Option<impl crate::arr::ArrayLikeIter<Val> + '_> {55		Some(56			self.inner57				.iter_cheap()?58				.skip(self.from as usize)59				.take((self.to - self.from) as usize)60				.step_by(self.step as usize),61		)62	}63}64impl ArrayLike for SliceArray {65	fn len(&self) -> usize {66		iter::repeat(())67			.take((self.to - self.from) as usize)68			.step_by(self.step as usize)69			.count()70	}7172	fn get(&self, index: usize) -> Result<Option<Val>> {73		self.iter().nth(index).transpose()74	}7576	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {77		self.iter_lazy().nth(index)78	}7980	fn get_cheap(&self, index: usize) -> Option<Val> {81		self.iter_cheap()?.nth(index)82	}83	fn is_cheap(&self) -> bool {84		self.inner.is_cheap()85	}86}8788#[derive(Trace, Debug)]89pub struct CharArray(pub Vec<char>);90impl ArrayLike for CharArray {91	fn len(&self) -> usize {92		self.0.len()93	}9495	fn get(&self, index: usize) -> Result<Option<Val>> {96		Ok(self.get_cheap(index))97	}9899	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {100		self.get_cheap(index).map(Thunk::evaluated)101	}102103	fn get_cheap(&self, index: usize) -> Option<Val> {104		self.0105			.get(index)106			.map(|v| Val::Str(StrValue::Flat(IStr::from(*v))))107	}108	fn is_cheap(&self) -> bool {109		true110	}111}112113#[derive(Trace, Debug)]114pub struct BytesArray(pub IBytes);115impl ArrayLike for BytesArray {116	fn len(&self) -> usize {117		self.0.len()118	}119120	fn get(&self, index: usize) -> Result<Option<Val>> {121		Ok(self.get_cheap(index))122	}123124	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {125		self.get_cheap(index).map(Thunk::evaluated)126	}127128	fn get_cheap(&self, index: usize) -> Option<Val> {129		self.0.get(index).map(|v| Val::Num(f64::from(*v)))130	}131	fn is_cheap(&self) -> bool {132		true133	}134}135136#[derive(Debug, Trace, Clone)]137enum ArrayThunk<T: 'static + Trace> {138	Computed(Val),139	Errored(Error),140	Waiting(T),141	Pending,142}143144#[derive(Debug, Trace, Clone)]145pub struct ExprArray {146	ctx: Context,147	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,148}149impl ExprArray {150	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {151		Self {152			ctx,153			cached: Cc::new(RefCell::new(154				items.into_iter().map(ArrayThunk::Waiting).collect(),155			)),156		}157	}158}159impl ArrayLike for ExprArray {160	fn len(&self) -> usize {161		self.cached.borrow().len()162	}163	fn get(&self, index: usize) -> Result<Option<Val>> {164		if index >= self.len() {165			return Ok(None);166		}167		match &self.cached.borrow()[index] {168			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),169			ArrayThunk::Errored(e) => return Err(e.clone()),170			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),171			ArrayThunk::Waiting(..) => {}172		};173174		let ArrayThunk::Waiting(expr) =175			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)176		else {177			unreachable!()178		};179180		let new_value = match evaluate(self.ctx.clone(), &expr) {181			Ok(v) => v,182			Err(e) => {183				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());184				return Err(e);185			}186		};187		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());188		Ok(Some(new_value))189	}190	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {191		#[derive(Trace)]192		struct ArrayElement {193			arr_thunk: ExprArray,194			index: usize,195		}196197		impl ThunkValue for ArrayElement {198			type Output = Val;199200			fn get(self: Box<Self>) -> Result<Self::Output> {201				self.arr_thunk202					.get(self.index)203					.transpose()204					.expect("index checked")205			}206		}207208		if index >= self.len() {209			return None;210		}211		match &self.cached.borrow()[index] {212			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),213			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),214			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}215		};216217		Some(Thunk::new(ArrayElement {218			arr_thunk: self.clone(),219			index,220		}))221	}222	fn get_cheap(&self, _index: usize) -> Option<Val> {223		None224	}225	fn is_cheap(&self) -> bool {226		false227	}228}229230#[derive(Trace, Debug)]231pub struct ExtendedArray {232	pub a: ArrValue,233	pub b: ArrValue,234	split: usize,235	len: usize,236}237impl ExtendedArray {238	pub fn new(a: ArrValue, b: ArrValue) -> Self {239		let a_len = a.len();240		let b_len = b.len();241		Self {242			a,243			b,244			split: a_len,245			len: a_len.checked_add(b_len).expect("too large array value"),246		}247	}248}249250struct WithExactSize<I>(I, usize);251impl<I, T> Iterator for WithExactSize<I>252where253	I: Iterator<Item = T>,254{255	type Item = T;256257	fn next(&mut self) -> Option<Self::Item> {258		self.0.next()259	}260	fn nth(&mut self, n: usize) -> Option<Self::Item> {261		self.0.nth(n)262	}263	fn size_hint(&self) -> (usize, Option<usize>) {264		(self.1, Some(self.1))265	}266}267impl<I> DoubleEndedIterator for WithExactSize<I>268where269	I: DoubleEndedIterator,270{271	fn next_back(&mut self) -> Option<Self::Item> {272		self.0.next_back()273	}274	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {275		self.0.nth_back(n)276	}277}278impl<I> ExactSizeIterator for WithExactSize<I>279where280	I: Iterator,281{282	fn len(&self) -> usize {283		self.1284	}285}286impl ArrayLike for ExtendedArray {287	fn get(&self, index: usize) -> Result<Option<Val>> {288		if self.split > index {289			self.a.get(index)290		} else {291			self.b.get(index - self.split)292		}293	}294	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {295		if self.split > index {296			self.a.get_lazy(index)297		} else {298			self.b.get_lazy(index - self.split)299		}300	}301302	fn len(&self) -> usize {303		self.len304	}305306	fn get_cheap(&self, index: usize) -> Option<Val> {307		if self.split > index {308			self.a.get_cheap(index)309		} else {310			self.b.get_cheap(index - self.split)311		}312	}313	fn is_cheap(&self) -> bool {314		self.a.is_cheap() && self.b.is_cheap()315	}316}317318#[derive(Trace, Debug)]319pub struct LazyArray(pub Vec<Thunk<Val>>);320impl ArrayLike for LazyArray {321	fn len(&self) -> usize {322		self.0.len()323	}324	fn get(&self, index: usize) -> Result<Option<Val>> {325		let Some(v) = self.0.get(index) else {326			return Ok(None);327		};328		v.evaluate().map(Some)329	}330	fn get_cheap(&self, _index: usize) -> Option<Val> {331		None332	}333	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {334		self.0.get(index).cloned()335	}336	fn is_cheap(&self) -> bool {337		false338	}339}340341#[derive(Trace, Debug)]342pub struct EagerArray(pub Vec<Val>);343impl ArrayLike for EagerArray {344	fn len(&self) -> usize {345		self.0.len()346	}347348	fn get(&self, index: usize) -> Result<Option<Val>> {349		Ok(self.0.get(index).cloned())350	}351352	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {353		self.0.get(index).cloned().map(Thunk::evaluated)354	}355356	fn get_cheap(&self, index: usize) -> Option<Val> {357		self.0.get(index).cloned()358	}359	fn is_cheap(&self) -> bool {360		true361	}362}363364/// Inclusive range type365#[derive(Debug, Trace, PartialEq, Eq)]366pub struct RangeArray {367	start: i32,368	end: i32,369}370impl RangeArray {371	pub fn empty() -> Self {372		Self::new_exclusive(0, 0)373	}374	pub fn new_exclusive(start: i32, end: i32) -> Self {375		end.checked_sub(1)376			.map_or_else(Self::empty, |end| Self { start, end })377	}378	pub fn new_inclusive(start: i32, end: i32) -> Self {379		Self { start, end }380	}381	fn range(&self) -> impl Iterator<Item = i32> + ExactSizeIterator + DoubleEndedIterator {382		WithExactSize(383			self.start..=self.end,384			(self.end as usize)385				.wrapping_sub(self.start as usize)386				.wrapping_add(1),387		)388	}389}390391impl ArrayLike for RangeArray {392	fn len(&self) -> usize {393		self.range().len()394	}395	fn is_empty(&self) -> bool {396		self.range().len() == 0397	}398399	fn get(&self, index: usize) -> Result<Option<Val>> {400		Ok(self.get_cheap(index))401	}402403	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {404		self.get_cheap(index).map(Thunk::evaluated)405	}406407	fn get_cheap(&self, index: usize) -> Option<Val> {408		self.range().nth(index).map(|i| Val::Num(f64::from(i)))409	}410	fn is_cheap(&self) -> bool {411		true412	}413}414415#[derive(Debug, Trace)]416pub struct ReverseArray(pub ArrValue);417impl ArrayLike for ReverseArray {418	fn len(&self) -> usize {419		self.0.len()420	}421422	fn get(&self, index: usize) -> Result<Option<Val>> {423		self.0.get(self.0.len() - index - 1)424	}425426	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {427		self.0.get_lazy(self.0.len() - index - 1)428	}429430	fn get_cheap(&self, index: usize) -> Option<Val> {431		self.0.get_cheap(self.0.len() - index - 1)432	}433	fn is_cheap(&self) -> bool {434		self.0.is_cheap()435	}436}437438#[derive(Trace, Debug, Clone)]439pub struct MappedArray {440	inner: ArrValue,441	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,442	mapper: FuncVal,443}444impl MappedArray {445	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {446		let len = inner.len();447		Self {448			inner,449			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),450			mapper,451		}452	}453}454impl ArrayLike for MappedArray {455	fn len(&self) -> usize {456		self.cached.borrow().len()457	}458459	fn get(&self, index: usize) -> Result<Option<Val>> {460		if index >= self.len() {461			return Ok(None);462		}463		match &self.cached.borrow()[index] {464			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),465			ArrayThunk::Errored(e) => return Err(e.clone()),466			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),467			ArrayThunk::Waiting(..) => {}468		};469470		let ArrayThunk::Waiting(_) =471			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)472		else {473			unreachable!()474		};475476		let val = self477			.inner478			.get(index)479			.transpose()480			.expect("index checked")481			.and_then(|r| self.mapper.evaluate_simple(&(r,), false));482483		let new_value = match val {484			Ok(v) => v,485			Err(e) => {486				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());487				return Err(e);488			}489		};490		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());491		Ok(Some(new_value))492	}493	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {494		#[derive(Trace)]495		struct ArrayElement {496			arr_thunk: MappedArray,497			index: usize,498		}499500		impl ThunkValue for ArrayElement {501			type Output = Val;502503			fn get(self: Box<Self>) -> Result<Self::Output> {504				self.arr_thunk505					.get(self.index)506					.transpose()507					.expect("index checked")508			}509		}510511		if index >= self.len() {512			return None;513		}514		match &self.cached.borrow()[index] {515			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),516			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),517			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}518		};519520		Some(Thunk::new(ArrayElement {521			arr_thunk: self.clone(),522			index,523		}))524	}525526	fn get_cheap(&self, _index: usize) -> Option<Val> {527		None528	}529	fn is_cheap(&self) -> bool {530		false531	}532}533534#[derive(Trace, Debug)]535pub struct RepeatedArray {536	data: ArrValue,537	repeats: usize,538	total_len: usize,539}540impl RepeatedArray {541	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {542		let total_len = data.len().checked_mul(repeats)?;543		Some(Self {544			data,545			repeats,546			total_len,547		})548	}549}550551impl ArrayLike for RepeatedArray {552	fn len(&self) -> usize {553		self.total_len554	}555556	fn get(&self, index: usize) -> Result<Option<Val>> {557		if index > self.total_len {558			return Ok(None);559		}560		self.data.get(index % self.data.len())561	}562563	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {564		if index > self.total_len {565			return None;566		}567		self.data.get_lazy(index % self.data.len())568	}569570	fn get_cheap(&self, index: usize) -> Option<Val> {571		if index > self.total_len {572			return None;573		}574		self.data.get_cheap(index % self.data.len())575	}576	fn is_cheap(&self) -> bool {577		self.data.is_cheap()578	}579}580581#[derive(Trace, Debug)]582pub struct PickObjectValues {583	obj: ObjValue,584	keys: Vec<IStr>,585}586587impl PickObjectValues {588	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {589		Self { obj, keys }590	}591}592593impl ArrayLike for PickObjectValues {594	fn len(&self) -> usize {595		self.keys.len()596	}597598	fn get(&self, index: usize) -> Result<Option<Val>> {599		let Some(key) = self.keys.get(index) else {600			return Ok(None);601		};602		Ok(Some(self.obj.get_or_bail(key.clone())?))603	}604605	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {606		let Some(key) = self.keys.get(index) else {607			return None;608		};609		Some(self.obj.get_lazy_or_bail(key.clone()))610	}611612	fn get_cheap(&self, _index: usize) -> Option<Val> {613		None614	}615616	fn is_cheap(&self) -> bool {617		false618	}619}620621#[derive(Trace, Debug)]622pub struct PickObjectKeyValues {623	obj: ObjValue,624	keys: Vec<IStr>,625}626627impl PickObjectKeyValues {628	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {629		Self { obj, keys }630	}631}632633#[derive(Typed)]634pub struct KeyValue {635	key: IStr,636	value: Thunk<Val>,637}638639impl ArrayLike for PickObjectKeyValues {640	fn len(&self) -> usize {641		self.keys.len()642	}643644	fn get(&self, index: usize) -> Result<Option<Val>> {645		let Some(key) = self.keys.get(index) else {646			return Ok(None);647		};648		Ok(Some(649			KeyValue::into_untyped(KeyValue {650				key: key.clone(),651				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),652			})653			.expect("convertible"),654		))655	}656657	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {658		let Some(key) = self.keys.get(index) else {659			return None;660		};661		// Nothing can fail in the key part, yet value is still662		// lazy-evaluated663		Some(Thunk::evaluated(664			KeyValue::into_untyped(KeyValue {665				key: key.clone(),666				value: self.obj.get_lazy_or_bail(key.clone()),667			})668			.expect("convertible"),669		))670	}671672	fn get_cheap(&self, _index: usize) -> Option<Val> {673		None674	}675676	fn is_cheap(&self) -> bool {677		false678	}679}
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]),