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}
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]),