git.delta.rocks / jrsonnet / refs/commits / 7cdcae351387

difftreelog

feat simplify Thunk creation with closure syntax

Yaroslav Bolyukin2024-08-26parent: #7d331b6.patch.diff
in: master

9 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, evaluate, function::FuncVal, typed::Typed,10	val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,11};1213pub trait ArrayLike: Any + Trace + Debug {14	fn len(&self) -> usize;15	fn is_empty(&self) -> bool {16		self.len() == 017	}18	fn get(&self, index: usize) -> Result<Option<Val>>;19	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;20	fn get_cheap(&self, index: usize) -> Option<Val>;2122	fn is_cheap(&self) -> bool;23}2425#[derive(Debug, Trace)]26pub struct SliceArray {27	pub(crate) inner: ArrValue,28	pub(crate) from: u32,29	pub(crate) to: u32,30	pub(crate) step: u32,31}3233impl SliceArray {34	fn iter(&self) -> impl Iterator<Item = Result<Val>> + '_ {35		self.inner36			.iter()37			.skip(self.from as usize)38			.take((self.to - self.from) as usize)39			.step_by(self.step as usize)40	}4142	fn iter_lazy(&self) -> impl Iterator<Item = Thunk<Val>> + '_ {43		self.inner44			.iter_lazy()45			.skip(self.from as usize)46			.take((self.to - self.from) as usize)47			.step_by(self.step as usize)48	}4950	fn iter_cheap(&self) -> Option<impl crate::arr::ArrayLikeIter<Val> + '_> {51		Some(52			self.inner53				.iter_cheap()?54				.skip(self.from as usize)55				.take((self.to - self.from) as usize)56				.step_by(self.step as usize),57		)58	}59}60impl ArrayLike for SliceArray {61	fn len(&self) -> usize {62		iter::repeat(())63			.take((self.to - self.from) as usize)64			.step_by(self.step as usize)65			.count()66	}6768	fn get(&self, index: usize) -> Result<Option<Val>> {69		self.iter().nth(index).transpose()70	}7172	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {73		self.iter_lazy().nth(index)74	}7576	fn get_cheap(&self, index: usize) -> Option<Val> {77		self.iter_cheap()?.nth(index)78	}79	fn is_cheap(&self) -> bool {80		self.inner.is_cheap()81	}82}8384#[derive(Trace, Debug)]85pub struct CharArray(pub Vec<char>);86impl ArrayLike for CharArray {87	fn len(&self) -> usize {88		self.0.len()89	}9091	fn get(&self, index: usize) -> Result<Option<Val>> {92		Ok(self.get_cheap(index))93	}9495	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {96		self.get_cheap(index).map(Thunk::evaluated)97	}9899	fn get_cheap(&self, index: usize) -> Option<Val> {100		self.0.get(index).map(|v| Val::string(*v))101	}102	fn is_cheap(&self) -> bool {103		true104	}105}106107#[derive(Trace, Debug)]108pub struct BytesArray(pub IBytes);109impl ArrayLike for BytesArray {110	fn len(&self) -> usize {111		self.0.len()112	}113114	fn get(&self, index: usize) -> Result<Option<Val>> {115		Ok(self.get_cheap(index))116	}117118	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {119		self.get_cheap(index).map(Thunk::evaluated)120	}121122	fn get_cheap(&self, index: usize) -> Option<Val> {123		self.0.get(index).map(|v| Val::Num((*v).into()))124	}125	fn is_cheap(&self) -> bool {126		true127	}128}129130#[derive(Debug, Trace, Clone)]131enum ArrayThunk<T: 'static + Trace> {132	Computed(Val),133	Errored(Error),134	Waiting(T),135	Pending,136}137138#[derive(Debug, Trace, Clone)]139pub struct ExprArray {140	ctx: Context,141	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,142}143impl ExprArray {144	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {145		Self {146			ctx,147			cached: Cc::new(RefCell::new(148				items.into_iter().map(ArrayThunk::Waiting).collect(),149			)),150		}151	}152}153impl ArrayLike for ExprArray {154	fn len(&self) -> usize {155		self.cached.borrow().len()156	}157	fn get(&self, index: usize) -> Result<Option<Val>> {158		if index >= self.len() {159			return Ok(None);160		}161		match &self.cached.borrow()[index] {162			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),163			ArrayThunk::Errored(e) => return Err(e.clone()),164			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),165			ArrayThunk::Waiting(..) => {}166		};167168		let ArrayThunk::Waiting(expr) =169			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)170		else {171			unreachable!()172		};173174		let new_value = match evaluate(self.ctx.clone(), &expr) {175			Ok(v) => v,176			Err(e) => {177				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());178				return Err(e);179			}180		};181		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());182		Ok(Some(new_value))183	}184	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {185		#[derive(Trace)]186		struct ArrayElement {187			arr_thunk: ExprArray,188			index: usize,189		}190191		impl ThunkValue for ArrayElement {192			type Output = Val;193194			fn get(self: Box<Self>) -> Result<Self::Output> {195				self.arr_thunk196					.get(self.index)197					.transpose()198					.expect("index checked")199			}200		}201202		if index >= self.len() {203			return None;204		}205		match &self.cached.borrow()[index] {206			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),207			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),208			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}209		};210211		Some(Thunk::new(ArrayElement {212			arr_thunk: self.clone(),213			index,214		}))215	}216	fn get_cheap(&self, _index: usize) -> Option<Val> {217		None218	}219	fn is_cheap(&self) -> bool {220		false221	}222}223224#[derive(Trace, Debug)]225pub struct ExtendedArray {226	pub a: ArrValue,227	pub b: ArrValue,228	split: usize,229	len: usize,230}231impl ExtendedArray {232	pub fn new(a: ArrValue, b: ArrValue) -> Self {233		let a_len = a.len();234		let b_len = b.len();235		Self {236			a,237			b,238			split: a_len,239			len: a_len.checked_add(b_len).expect("too large array value"),240		}241	}242}243244struct WithExactSize<I>(I, usize);245impl<I, T> Iterator for WithExactSize<I>246where247	I: Iterator<Item = T>,248{249	type Item = T;250251	fn next(&mut self) -> Option<Self::Item> {252		self.0.next()253	}254	fn nth(&mut self, n: usize) -> Option<Self::Item> {255		self.0.nth(n)256	}257	fn size_hint(&self) -> (usize, Option<usize>) {258		(self.1, Some(self.1))259	}260}261impl<I> DoubleEndedIterator for WithExactSize<I>262where263	I: DoubleEndedIterator,264{265	fn next_back(&mut self) -> Option<Self::Item> {266		self.0.next_back()267	}268	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {269		self.0.nth_back(n)270	}271}272impl<I> ExactSizeIterator for WithExactSize<I>273where274	I: Iterator,275{276	fn len(&self) -> usize {277		self.1278	}279}280impl ArrayLike for ExtendedArray {281	fn get(&self, index: usize) -> Result<Option<Val>> {282		if self.split > index {283			self.a.get(index)284		} else {285			self.b.get(index - self.split)286		}287	}288	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {289		if self.split > index {290			self.a.get_lazy(index)291		} else {292			self.b.get_lazy(index - self.split)293		}294	}295296	fn len(&self) -> usize {297		self.len298	}299300	fn get_cheap(&self, index: usize) -> Option<Val> {301		if self.split > index {302			self.a.get_cheap(index)303		} else {304			self.b.get_cheap(index - self.split)305		}306	}307	fn is_cheap(&self) -> bool {308		self.a.is_cheap() && self.b.is_cheap()309	}310}311312#[derive(Trace, Debug)]313pub struct LazyArray(pub Vec<Thunk<Val>>);314impl ArrayLike for LazyArray {315	fn len(&self) -> usize {316		self.0.len()317	}318	fn get(&self, index: usize) -> Result<Option<Val>> {319		let Some(v) = self.0.get(index) else {320			return Ok(None);321		};322		v.evaluate().map(Some)323	}324	fn get_cheap(&self, _index: usize) -> Option<Val> {325		None326	}327	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {328		self.0.get(index).cloned()329	}330	fn is_cheap(&self) -> bool {331		false332	}333}334335#[derive(Trace, Debug)]336pub struct EagerArray(pub Vec<Val>);337impl ArrayLike for EagerArray {338	fn len(&self) -> usize {339		self.0.len()340	}341342	fn get(&self, index: usize) -> Result<Option<Val>> {343		Ok(self.0.get(index).cloned())344	}345346	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {347		self.0.get(index).cloned().map(Thunk::evaluated)348	}349350	fn get_cheap(&self, index: usize) -> Option<Val> {351		self.0.get(index).cloned()352	}353	fn is_cheap(&self) -> bool {354		true355	}356}357358/// Inclusive range type359#[derive(Debug, Trace, PartialEq, Eq)]360pub struct RangeArray {361	start: i32,362	end: i32,363}364impl RangeArray {365	pub fn empty() -> Self {366		Self::new_exclusive(0, 0)367	}368	pub fn new_exclusive(start: i32, end: i32) -> Self {369		end.checked_sub(1)370			.map_or_else(Self::empty, |end| Self { start, end })371	}372	pub fn new_inclusive(start: i32, end: i32) -> Self {373		Self { start, end }374	}375	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {376		WithExactSize(377			self.start..=self.end,378			(self.end as usize)379				.wrapping_sub(self.start as usize)380				.wrapping_add(1),381		)382	}383}384385impl ArrayLike for RangeArray {386	fn len(&self) -> usize {387		self.range().len()388	}389	fn is_empty(&self) -> bool {390		self.range().len() == 0391	}392393	fn get(&self, index: usize) -> Result<Option<Val>> {394		Ok(self.get_cheap(index))395	}396397	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {398		self.get_cheap(index).map(Thunk::evaluated)399	}400401	fn get_cheap(&self, index: usize) -> Option<Val> {402		self.range().nth(index).map(|i| Val::Num(i.into()))403	}404	fn is_cheap(&self) -> bool {405		true406	}407}408409#[derive(Debug, Trace)]410pub struct ReverseArray(pub ArrValue);411impl ArrayLike for ReverseArray {412	fn len(&self) -> usize {413		self.0.len()414	}415416	fn get(&self, index: usize) -> Result<Option<Val>> {417		self.0.get(self.0.len() - index - 1)418	}419420	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {421		self.0.get_lazy(self.0.len() - index - 1)422	}423424	fn get_cheap(&self, index: usize) -> Option<Val> {425		self.0.get_cheap(self.0.len() - index - 1)426	}427	fn is_cheap(&self) -> bool {428		self.0.is_cheap()429	}430}431432#[derive(Trace, Debug, Clone)]433pub struct MappedArray<const WITH_INDEX: bool> {434	inner: ArrValue,435	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,436	mapper: FuncVal,437}438impl<const WITH_INDEX: bool> MappedArray<WITH_INDEX> {439	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {440		let len = inner.len();441		Self {442			inner,443			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),444			mapper,445		}446	}447	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {448		if WITH_INDEX {449			self.mapper.evaluate_simple(&(index, value), false)450		} else {451			self.mapper.evaluate_simple(&(value,), false)452		}453	}454}455impl<const WITH_INDEX: bool> ArrayLike for MappedArray<WITH_INDEX> {456	fn len(&self) -> usize {457		self.cached.borrow().len()458	}459460	fn get(&self, index: usize) -> Result<Option<Val>> {461		if index >= self.len() {462			return Ok(None);463		}464		match &self.cached.borrow()[index] {465			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),466			ArrayThunk::Errored(e) => return Err(e.clone()),467			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),468			ArrayThunk::Waiting(..) => {}469		};470471		let ArrayThunk::Waiting(()) =472			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)473		else {474			unreachable!()475		};476477		let val = self478			.inner479			.get(index)480			.transpose()481			.expect("index checked")482			.and_then(|r| self.evaluate(index, r));483484		let new_value = match val {485			Ok(v) => v,486			Err(e) => {487				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());488				return Err(e);489			}490		};491		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());492		Ok(Some(new_value))493	}494	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {495		#[derive(Trace)]496		struct ArrayElement<const WITH_INDEX: bool> {497			arr_thunk: MappedArray<WITH_INDEX>,498			index: usize,499		}500501		impl<const WITH_INDEX: bool> ThunkValue for ArrayElement<WITH_INDEX> {502			type Output = Val;503504			fn get(self: Box<Self>) -> Result<Self::Output> {505				self.arr_thunk506					.get(self.index)507					.transpose()508					.expect("index checked")509			}510		}511512		if index >= self.len() {513			return None;514		}515		match &self.cached.borrow()[index] {516			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),517			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),518			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}519		};520521		Some(Thunk::new(ArrayElement {522			arr_thunk: self.clone(),523			index,524		}))525	}526527	fn get_cheap(&self, _index: usize) -> Option<Val> {528		None529	}530	fn is_cheap(&self) -> bool {531		false532	}533}534535#[derive(Trace, Debug)]536pub struct RepeatedArray {537	data: ArrValue,538	repeats: usize,539	total_len: usize,540}541impl RepeatedArray {542	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {543		let total_len = data.len().checked_mul(repeats)?;544		Some(Self {545			data,546			repeats,547			total_len,548		})549	}550}551552impl ArrayLike for RepeatedArray {553	fn len(&self) -> usize {554		self.total_len555	}556557	fn get(&self, index: usize) -> Result<Option<Val>> {558		if index > self.total_len {559			return Ok(None);560		}561		self.data.get(index % self.data.len())562	}563564	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {565		if index > self.total_len {566			return None;567		}568		self.data.get_lazy(index % self.data.len())569	}570571	fn get_cheap(&self, index: usize) -> Option<Val> {572		if index > self.total_len {573			return None;574		}575		self.data.get_cheap(index % self.data.len())576	}577	fn is_cheap(&self) -> bool {578		self.data.is_cheap()579	}580}581582#[derive(Trace, Debug)]583pub struct PickObjectValues {584	obj: ObjValue,585	keys: Vec<IStr>,586}587588impl PickObjectValues {589	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {590		Self { obj, keys }591	}592}593594impl ArrayLike for PickObjectValues {595	fn len(&self) -> usize {596		self.keys.len()597	}598599	fn get(&self, index: usize) -> Result<Option<Val>> {600		let Some(key) = self.keys.get(index) else {601			return Ok(None);602		};603		Ok(Some(self.obj.get_or_bail(key.clone())?))604	}605606	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {607		let key = self.keys.get(index)?;608		Some(self.obj.get_lazy_or_bail(key.clone()))609	}610611	fn get_cheap(&self, _index: usize) -> Option<Val> {612		None613	}614615	fn is_cheap(&self) -> bool {616		false617	}618}619620#[derive(Trace, Debug)]621pub struct PickObjectKeyValues {622	obj: ObjValue,623	keys: Vec<IStr>,624}625626impl PickObjectKeyValues {627	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {628		Self { obj, keys }629	}630}631632#[derive(Typed)]633pub struct KeyValue {634	key: IStr,635	value: Thunk<Val>,636}637638impl ArrayLike for PickObjectKeyValues {639	fn len(&self) -> usize {640		self.keys.len()641	}642643	fn get(&self, index: usize) -> Result<Option<Val>> {644		let Some(key) = self.keys.get(index) else {645			return Ok(None);646		};647		Ok(Some(648			KeyValue::into_untyped(KeyValue {649				key: key.clone(),650				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),651			})652			.expect("convertible"),653		))654	}655656	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {657		let key = self.keys.get(index)?;658		// Nothing can fail in the key part, yet value is still659		// lazy-evaluated660		Some(Thunk::evaluated(661			KeyValue::into_untyped(KeyValue {662				key: key.clone(),663				value: self.obj.get_lazy_or_bail(key.clone()),664			})665			.expect("convertible"),666		))667	}668669	fn get_cheap(&self, _index: usize) -> Option<Val> {670		None671	}672673	fn is_cheap(&self) -> bool {674		false675	}676}
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, evaluate, function::FuncVal, typed::Typed,10	Context, Error, ObjValue, Result, Thunk, Val,11};1213pub trait ArrayLike: Any + Trace + Debug {14	fn len(&self) -> usize;15	fn is_empty(&self) -> bool {16		self.len() == 017	}18	fn get(&self, index: usize) -> Result<Option<Val>>;19	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;20	fn get_cheap(&self, index: usize) -> Option<Val>;2122	fn is_cheap(&self) -> bool;23}2425#[derive(Debug, Trace)]26pub struct SliceArray {27	pub(crate) inner: ArrValue,28	pub(crate) from: u32,29	pub(crate) to: u32,30	pub(crate) step: u32,31}3233impl SliceArray {34	fn iter(&self) -> impl Iterator<Item = Result<Val>> + '_ {35		self.inner36			.iter()37			.skip(self.from as usize)38			.take((self.to - self.from) as usize)39			.step_by(self.step as usize)40	}4142	fn iter_lazy(&self) -> impl Iterator<Item = Thunk<Val>> + '_ {43		self.inner44			.iter_lazy()45			.skip(self.from as usize)46			.take((self.to - self.from) as usize)47			.step_by(self.step as usize)48	}4950	fn iter_cheap(&self) -> Option<impl crate::arr::ArrayLikeIter<Val> + '_> {51		Some(52			self.inner53				.iter_cheap()?54				.skip(self.from as usize)55				.take((self.to - self.from) as usize)56				.step_by(self.step as usize),57		)58	}59}60impl ArrayLike for SliceArray {61	fn len(&self) -> usize {62		iter::repeat(())63			.take((self.to - self.from) as usize)64			.step_by(self.step as usize)65			.count()66	}6768	fn get(&self, index: usize) -> Result<Option<Val>> {69		self.iter().nth(index).transpose()70	}7172	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {73		self.iter_lazy().nth(index)74	}7576	fn get_cheap(&self, index: usize) -> Option<Val> {77		self.iter_cheap()?.nth(index)78	}79	fn is_cheap(&self) -> bool {80		self.inner.is_cheap()81	}82}8384#[derive(Trace, Debug)]85pub struct CharArray(pub Vec<char>);86impl ArrayLike for CharArray {87	fn len(&self) -> usize {88		self.0.len()89	}9091	fn get(&self, index: usize) -> Result<Option<Val>> {92		Ok(self.get_cheap(index))93	}9495	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {96		self.get_cheap(index).map(Thunk::evaluated)97	}9899	fn get_cheap(&self, index: usize) -> Option<Val> {100		self.0.get(index).map(|v| Val::string(*v))101	}102	fn is_cheap(&self) -> bool {103		true104	}105}106107#[derive(Trace, Debug)]108pub struct BytesArray(pub IBytes);109impl ArrayLike for BytesArray {110	fn len(&self) -> usize {111		self.0.len()112	}113114	fn get(&self, index: usize) -> Result<Option<Val>> {115		Ok(self.get_cheap(index))116	}117118	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {119		self.get_cheap(index).map(Thunk::evaluated)120	}121122	fn get_cheap(&self, index: usize) -> Option<Val> {123		self.0.get(index).map(|v| Val::Num((*v).into()))124	}125	fn is_cheap(&self) -> bool {126		true127	}128}129130#[derive(Debug, Trace, Clone)]131enum ArrayThunk<T: 'static + Trace> {132	Computed(Val),133	Errored(Error),134	Waiting(T),135	Pending,136}137138#[derive(Debug, Trace, Clone)]139pub struct ExprArray {140	ctx: Context,141	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,142}143impl ExprArray {144	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {145		Self {146			ctx,147			cached: Cc::new(RefCell::new(148				items.into_iter().map(ArrayThunk::Waiting).collect(),149			)),150		}151	}152}153impl ArrayLike for ExprArray {154	fn len(&self) -> usize {155		self.cached.borrow().len()156	}157	fn get(&self, index: usize) -> Result<Option<Val>> {158		if index >= self.len() {159			return Ok(None);160		}161		match &self.cached.borrow()[index] {162			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),163			ArrayThunk::Errored(e) => return Err(e.clone()),164			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),165			ArrayThunk::Waiting(..) => {}166		};167168		let ArrayThunk::Waiting(expr) =169			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)170		else {171			unreachable!()172		};173174		let new_value = match evaluate(self.ctx.clone(), &expr) {175			Ok(v) => v,176			Err(e) => {177				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());178				return Err(e);179			}180		};181		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());182		Ok(Some(new_value))183	}184	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {185		if index >= self.len() {186			return None;187		}188		match &self.cached.borrow()[index] {189			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),190			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),191			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}192		};193194		let arr_thunk = self.clone();195		Some(Thunk!(move || {196			arr_thunk.get(index).transpose().expect("index checked")197		}))198	}199	fn get_cheap(&self, _index: usize) -> Option<Val> {200		None201	}202	fn is_cheap(&self) -> bool {203		false204	}205}206207#[derive(Trace, Debug)]208pub struct ExtendedArray {209	pub a: ArrValue,210	pub b: ArrValue,211	split: usize,212	len: usize,213}214impl ExtendedArray {215	pub fn new(a: ArrValue, b: ArrValue) -> Self {216		let a_len = a.len();217		let b_len = b.len();218		Self {219			a,220			b,221			split: a_len,222			len: a_len.checked_add(b_len).expect("too large array value"),223		}224	}225}226227struct WithExactSize<I>(I, usize);228impl<I, T> Iterator for WithExactSize<I>229where230	I: Iterator<Item = T>,231{232	type Item = T;233234	fn next(&mut self) -> Option<Self::Item> {235		self.0.next()236	}237	fn nth(&mut self, n: usize) -> Option<Self::Item> {238		self.0.nth(n)239	}240	fn size_hint(&self) -> (usize, Option<usize>) {241		(self.1, Some(self.1))242	}243}244impl<I> DoubleEndedIterator for WithExactSize<I>245where246	I: DoubleEndedIterator,247{248	fn next_back(&mut self) -> Option<Self::Item> {249		self.0.next_back()250	}251	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {252		self.0.nth_back(n)253	}254}255impl<I> ExactSizeIterator for WithExactSize<I>256where257	I: Iterator,258{259	fn len(&self) -> usize {260		self.1261	}262}263impl ArrayLike for ExtendedArray {264	fn get(&self, index: usize) -> Result<Option<Val>> {265		if self.split > index {266			self.a.get(index)267		} else {268			self.b.get(index - self.split)269		}270	}271	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {272		if self.split > index {273			self.a.get_lazy(index)274		} else {275			self.b.get_lazy(index - self.split)276		}277	}278279	fn len(&self) -> usize {280		self.len281	}282283	fn get_cheap(&self, index: usize) -> Option<Val> {284		if self.split > index {285			self.a.get_cheap(index)286		} else {287			self.b.get_cheap(index - self.split)288		}289	}290	fn is_cheap(&self) -> bool {291		self.a.is_cheap() && self.b.is_cheap()292	}293}294295#[derive(Trace, Debug)]296pub struct LazyArray(pub Vec<Thunk<Val>>);297impl ArrayLike for LazyArray {298	fn len(&self) -> usize {299		self.0.len()300	}301	fn get(&self, index: usize) -> Result<Option<Val>> {302		let Some(v) = self.0.get(index) else {303			return Ok(None);304		};305		v.evaluate().map(Some)306	}307	fn get_cheap(&self, _index: usize) -> Option<Val> {308		None309	}310	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {311		self.0.get(index).cloned()312	}313	fn is_cheap(&self) -> bool {314		false315	}316}317318#[derive(Trace, Debug)]319pub struct EagerArray(pub Vec<Val>);320impl ArrayLike for EagerArray {321	fn len(&self) -> usize {322		self.0.len()323	}324325	fn get(&self, index: usize) -> Result<Option<Val>> {326		Ok(self.0.get(index).cloned())327	}328329	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {330		self.0.get(index).cloned().map(Thunk::evaluated)331	}332333	fn get_cheap(&self, index: usize) -> Option<Val> {334		self.0.get(index).cloned()335	}336	fn is_cheap(&self) -> bool {337		true338	}339}340341/// Inclusive range type342#[derive(Debug, Trace, PartialEq, Eq)]343pub struct RangeArray {344	start: i32,345	end: i32,346}347impl RangeArray {348	pub fn empty() -> Self {349		Self::new_exclusive(0, 0)350	}351	pub fn new_exclusive(start: i32, end: i32) -> Self {352		end.checked_sub(1)353			.map_or_else(Self::empty, |end| Self { start, end })354	}355	pub fn new_inclusive(start: i32, end: i32) -> Self {356		Self { start, end }357	}358	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {359		WithExactSize(360			self.start..=self.end,361			(self.end as usize)362				.wrapping_sub(self.start as usize)363				.wrapping_add(1),364		)365	}366}367368impl ArrayLike for RangeArray {369	fn len(&self) -> usize {370		self.range().len()371	}372	fn is_empty(&self) -> bool {373		self.range().len() == 0374	}375376	fn get(&self, index: usize) -> Result<Option<Val>> {377		Ok(self.get_cheap(index))378	}379380	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {381		self.get_cheap(index).map(Thunk::evaluated)382	}383384	fn get_cheap(&self, index: usize) -> Option<Val> {385		self.range().nth(index).map(|i| Val::Num(i.into()))386	}387	fn is_cheap(&self) -> bool {388		true389	}390}391392#[derive(Debug, Trace)]393pub struct ReverseArray(pub ArrValue);394impl ArrayLike for ReverseArray {395	fn len(&self) -> usize {396		self.0.len()397	}398399	fn get(&self, index: usize) -> Result<Option<Val>> {400		self.0.get(self.0.len() - index - 1)401	}402403	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {404		self.0.get_lazy(self.0.len() - index - 1)405	}406407	fn get_cheap(&self, index: usize) -> Option<Val> {408		self.0.get_cheap(self.0.len() - index - 1)409	}410	fn is_cheap(&self) -> bool {411		self.0.is_cheap()412	}413}414415#[derive(Trace, Debug, Clone)]416pub struct MappedArray<const WITH_INDEX: bool> {417	inner: ArrValue,418	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,419	mapper: FuncVal,420}421impl<const WITH_INDEX: bool> MappedArray<WITH_INDEX> {422	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {423		let len = inner.len();424		Self {425			inner,426			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),427			mapper,428		}429	}430	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {431		if WITH_INDEX {432			self.mapper.evaluate_simple(&(index, value), false)433		} else {434			self.mapper.evaluate_simple(&(value,), false)435		}436	}437}438impl<const WITH_INDEX: bool> ArrayLike for MappedArray<WITH_INDEX> {439	fn len(&self) -> usize {440		self.cached.borrow().len()441	}442443	fn get(&self, index: usize) -> Result<Option<Val>> {444		if index >= self.len() {445			return Ok(None);446		}447		match &self.cached.borrow()[index] {448			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),449			ArrayThunk::Errored(e) => return Err(e.clone()),450			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),451			ArrayThunk::Waiting(..) => {}452		};453454		let ArrayThunk::Waiting(()) =455			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)456		else {457			unreachable!()458		};459460		let val = self461			.inner462			.get(index)463			.transpose()464			.expect("index checked")465			.and_then(|r| self.evaluate(index, r));466467		let new_value = match val {468			Ok(v) => v,469			Err(e) => {470				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());471				return Err(e);472			}473		};474		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());475		Ok(Some(new_value))476	}477	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {478		if index >= self.len() {479			return None;480		}481		match &self.cached.borrow()[index] {482			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),483			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),484			ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}485		};486487		let arr_thunk = self.clone();488		Some(Thunk!(move || {489			arr_thunk.get(index).transpose().expect("index checked")490		}))491	}492493	fn get_cheap(&self, _index: usize) -> Option<Val> {494		None495	}496	fn is_cheap(&self) -> bool {497		false498	}499}500501#[derive(Trace, Debug)]502pub struct RepeatedArray {503	data: ArrValue,504	repeats: usize,505	total_len: usize,506}507impl RepeatedArray {508	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {509		let total_len = data.len().checked_mul(repeats)?;510		Some(Self {511			data,512			repeats,513			total_len,514		})515	}516}517518impl ArrayLike for RepeatedArray {519	fn len(&self) -> usize {520		self.total_len521	}522523	fn get(&self, index: usize) -> Result<Option<Val>> {524		if index > self.total_len {525			return Ok(None);526		}527		self.data.get(index % self.data.len())528	}529530	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {531		if index > self.total_len {532			return None;533		}534		self.data.get_lazy(index % self.data.len())535	}536537	fn get_cheap(&self, index: usize) -> Option<Val> {538		if index > self.total_len {539			return None;540		}541		self.data.get_cheap(index % self.data.len())542	}543	fn is_cheap(&self) -> bool {544		self.data.is_cheap()545	}546}547548#[derive(Trace, Debug)]549pub struct PickObjectValues {550	obj: ObjValue,551	keys: Vec<IStr>,552}553554impl PickObjectValues {555	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {556		Self { obj, keys }557	}558}559560impl ArrayLike for PickObjectValues {561	fn len(&self) -> usize {562		self.keys.len()563	}564565	fn get(&self, index: usize) -> Result<Option<Val>> {566		let Some(key) = self.keys.get(index) else {567			return Ok(None);568		};569		Ok(Some(self.obj.get_or_bail(key.clone())?))570	}571572	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {573		let key = self.keys.get(index)?;574		Some(self.obj.get_lazy_or_bail(key.clone()))575	}576577	fn get_cheap(&self, _index: usize) -> Option<Val> {578		None579	}580581	fn is_cheap(&self) -> bool {582		false583	}584}585586#[derive(Trace, Debug)]587pub struct PickObjectKeyValues {588	obj: ObjValue,589	keys: Vec<IStr>,590}591592impl PickObjectKeyValues {593	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {594		Self { obj, keys }595	}596}597598#[derive(Typed)]599pub struct KeyValue {600	key: IStr,601	value: Thunk<Val>,602}603604impl ArrayLike for PickObjectKeyValues {605	fn len(&self) -> usize {606		self.keys.len()607	}608609	fn get(&self, index: usize) -> Result<Option<Val>> {610		let Some(key) = self.keys.get(index) else {611			return Ok(None);612		};613		Ok(Some(614			KeyValue::into_untyped(KeyValue {615				key: key.clone(),616				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),617			})618			.expect("convertible"),619		))620	}621622	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {623		let key = self.keys.get(index)?;624		// Nothing can fail in the key part, yet value is still625		// lazy-evaluated626		Some(Thunk::evaluated(627			KeyValue::into_untyped(KeyValue {628				key: key.clone(),629				value: self.obj.get_lazy_or_bail(key.clone()),630			})631			.expect("convertible"),632		))633	}634635	fn get_cheap(&self, _index: usize) -> Option<Val> {636		None637	}638639	fn is_cheap(&self) -> bool {640		false641	}642}
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,13 +1,11 @@
-use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};
+use jrsonnet_parser::{BindSpec, Destruct};
 
 use crate::{
 	bail,
 	error::{ErrorKind::*, Result},
 	evaluate, evaluate_method, evaluate_named,
 	gc::GcHashMap,
-	val::ThunkValue,
 	Context, Pending, Thunk, Val,
 };
 
@@ -31,65 +29,34 @@
 		#[cfg(feature = "exp-destruct")]
 		Destruct::Array { start, rest, end } => {
 			use jrsonnet_parser::DestructRest;
-
-			use crate::arr::ArrValue;
-
-			#[derive(Trace)]
-			struct DataThunk {
-				parent: Thunk<Val>,
-				min_len: usize,
-				has_rest: bool,
-			}
-			impl ThunkValue for DataThunk {
-				type Output = ArrValue;
 
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					let v = self.parent.evaluate()?;
-					let Val::Arr(arr) = v else {
-						bail!("expected array");
-					};
-					if !self.has_rest {
-						if arr.len() != self.min_len {
-							bail!("expected {} elements, got {}", self.min_len, arr.len())
-						}
-					} else if arr.len() < self.min_len {
-						bail!(
-							"expected at least {} elements, but array was only {}",
-							self.min_len,
-							arr.len()
-						)
+			let min_len = start.len() + end.len();
+			let has_rest = rest.is_some();
+			let full = Thunk!(move || {
+				let v = parent.evaluate()?;
+				let Val::Arr(arr) = v else {
+					bail!("expected array");
+				};
+				if !has_rest {
+					if arr.len() != min_len {
+						bail!("expected {} elements, got {}", min_len, arr.len())
 					}
-					Ok(arr)
+				} else if arr.len() < min_len {
+					bail!(
+						"expected at least {} elements, but array was only {}",
+						min_len,
+						arr.len()
+					)
 				}
-			}
-
-			let full = Thunk::new(DataThunk {
-				min_len: start.len() + end.len(),
-				has_rest: rest.is_some(),
-				parent,
+				Ok(arr)
 			});
 
 			{
-				#[derive(Trace)]
-				struct BaseThunk {
-					full: Thunk<ArrValue>,
-					index: usize,
-				}
-				impl ThunkValue for BaseThunk {
-					type Output = Val;
-
-					fn get(self: Box<Self>) -> Result<Self::Output> {
-						let full = self.full.evaluate()?;
-						Ok(full.get(self.index)?.expect("length is checked"))
-					}
-				}
 				for (i, d) in start.iter().enumerate() {
+					let full = full.clone();
 					destruct(
 						d,
-						Thunk::new(BaseThunk {
-							full: full.clone(),
-							index: i,
-						}),
+						Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),
 						fctx.clone(),
 						new_bindings,
 					)?;
@@ -98,32 +65,19 @@
 
 			match rest {
 				Some(DestructRest::Keep(v)) => {
-					#[derive(Trace)]
-					struct RestThunk {
-						full: Thunk<ArrValue>,
-						start: usize,
-						end: usize,
-					}
-					impl ThunkValue for RestThunk {
-						type Output = Val;
-
-						fn get(self: Box<Self>) -> Result<Self::Output> {
-							let full = self.full.evaluate()?;
-							let to = full.len() - self.end;
+					let start = start.len();
+					let end = end.len();
+					let full = full.clone();
+					destruct(
+						&Destruct::Full(v.clone()),
+						Thunk!(move || {
+							let full = full.evaluate()?;
+							let to = full.len() - end;
 							Ok(Val::Arr(full.slice(
-								Some(self.start as i32),
+								Some(start as i32),
 								Some(to as i32),
 								None,
 							)))
-						}
-					}
-
-					destruct(
-						&Destruct::Full(v.clone()),
-						Thunk::new(RestThunk {
-							full: full.clone(),
-							start: start.len(),
-							end: end.len(),
 						}),
 						fctx.clone(),
 						new_bindings,
@@ -133,29 +87,14 @@
 			}
 
 			{
-				#[derive(Trace)]
-				struct EndThunk {
-					full: Thunk<ArrValue>,
-					index: usize,
-					end: usize,
-				}
-				impl ThunkValue for EndThunk {
-					type Output = Val;
-
-					fn get(self: Box<Self>) -> Result<Self::Output> {
-						let full = self.full.evaluate()?;
-						Ok(full
-							.get(full.len() - self.end + self.index)?
-							.expect("length is checked"))
-					}
-				}
 				for (i, d) in end.iter().enumerate() {
+					let full = full.clone();
+					let end = end.len();
 					destruct(
 						d,
-						Thunk::new(EndThunk {
-							full: full.clone(),
-							index: i,
-							end: end.len(),
+						Thunk!(move || {
+							let full = full.evaluate()?;
+							Ok(full.get(full.len() - end + i)?.expect("length is checked"))
 						}),
 						fctx.clone(),
 						new_bindings,
@@ -165,71 +104,46 @@
 		}
 		#[cfg(feature = "exp-destruct")]
 		Destruct::Object { fields, rest } => {
-			use crate::obj::ObjValue;
-
-			#[derive(Trace)]
-			struct DataThunk {
-				parent: Thunk<Val>,
-				field_names: Vec<(IStr, bool)>,
-				has_rest: bool,
-			}
-			impl ThunkValue for DataThunk {
-				type Output = ObjValue;
-
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					let v = self.parent.evaluate()?;
-					let Val::Obj(obj) = v else {
-						bail!("expected object");
-					};
-					for (field, has_default) in &self.field_names {
-						if !has_default && !obj.has_field_ex(field.clone(), true) {
-							bail!("missing field: {field}");
-						}
-					}
-					if !self.has_rest {
-						let len = obj.len();
-						if len > self.field_names.len() {
-							bail!("too many fields, and rest not found");
-						}
-					}
-					Ok(obj)
-				}
-			}
 			let field_names: Vec<_> = fields
 				.iter()
 				.map(|f| (f.0.clone(), f.2.is_some()))
 				.collect();
-			let full = Thunk::new(DataThunk {
-				parent,
-				field_names,
-				has_rest: rest.is_some(),
+			let has_rest = rest.is_some();
+			let full = Thunk!(move || {
+				let v = parent.evaluate()?;
+				let Val::Obj(obj) = v else {
+					bail!("expected object");
+				};
+				for (field, has_default) in &field_names {
+					if !has_default && !obj.has_field_ex(field.clone(), true) {
+						bail!("missing field: {field}");
+					}
+				}
+				if !has_rest {
+					let len = obj.len();
+					if len > field_names.len() {
+						bail!("too many fields, and rest not found");
+					}
+				}
+				Ok(obj)
 			});
 
 			for (field, d, default) in fields {
-				#[derive(Trace)]
-				struct FieldThunk {
-					full: Thunk<ObjValue>,
-					field: IStr,
-					default: Option<(Pending<Context>, LocExpr)>,
-				}
-				impl ThunkValue for FieldThunk {
-					type Output = Val;
-
-					fn get(self: Box<Self>) -> Result<Self::Output> {
-						let full = self.full.evaluate()?;
-						if let Some(field) = full.get(self.field)? {
+				let default = default.clone().map(|e| (fctx.clone(), e));
+				let value = {
+					let field = field.clone();
+					let full = full.clone();
+					Thunk!(move || {
+						let full = full.evaluate()?;
+						if let Some(field) = full.get(field)? {
 							Ok(field)
 						} else {
-							let (fctx, expr) = self.default.as_ref().expect("shape is checked");
+							let (fctx, expr) = default.as_ref().expect("shape is checked");
 							Ok(evaluate(fctx.clone().unwrap(), expr)?)
 						}
-					}
-				}
-				let value = Thunk::new(FieldThunk {
-					full: full.clone(),
-					field: field.clone(),
-					default: default.clone().map(|e| (fctx.clone(), e)),
-				});
+					})
+				};
+
 				if let Some(d) = d {
 					destruct(d, value, fctx.clone(), new_bindings)?;
 				} else {
@@ -253,26 +167,15 @@
 ) -> Result<()> {
 	match d {
 		BindSpec::Field { into, value } => {
-			#[derive(Trace)]
-			struct EvaluateThunkValue {
-				name: Option<IStr>,
-				fctx: Pending<Context>,
-				expr: LocExpr,
-			}
-			impl ThunkValue for EvaluateThunkValue {
-				type Output = Val;
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					self.name.map_or_else(
-						|| evaluate(self.fctx.unwrap(), &self.expr),
-						|name| evaluate_named(self.fctx.unwrap(), &self.expr, name),
-					)
-				}
-			}
-			let data = Thunk::new(EvaluateThunkValue {
-				name: into.name(),
-				fctx: fctx.clone(),
-				expr: value.clone(),
-			});
+			let name = into.name();
+			let value = value.clone();
+			let data = {
+				let fctx = fctx.clone();
+				Thunk!(move || name.map_or_else(
+					|| evaluate(fctx.unwrap(), &value),
+					|name| evaluate_named(fctx.unwrap(), &value, name),
+				))
+			};
 			destruct(into, data, fctx, new_bindings)?;
 		}
 		BindSpec::Function {
@@ -280,37 +183,15 @@
 			params,
 			value,
 		} => {
-			#[derive(Trace)]
-			struct MethodThunk {
-				fctx: Pending<Context>,
-				name: IStr,
-				params: ParamsDesc,
-				value: LocExpr,
-			}
-			impl ThunkValue for MethodThunk {
-				type Output = Val;
-
-				fn get(self: Box<Self>) -> Result<Self::Output> {
-					Ok(evaluate_method(
-						self.fctx.unwrap(),
-						self.name,
-						self.params,
-						self.value,
-					))
-				}
-			}
-
-			let old = new_bindings.insert(
-				name.clone(),
-				Thunk::new(MethodThunk {
-					fctx,
-					name: name.clone(),
-					params: params.clone(),
-					value: value.clone(),
-				}),
-			);
+			let params = params.clone();
+			let name = name.clone();
+			let value = value.clone();
+			let old = new_bindings.insert(name.clone(), {
+				let name = name.clone();
+				Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))
+			});
 			if old.is_some() {
-				bail!(DuplicateLocalVar(name.clone()))
+				bail!(DuplicateLocalVar(name))
 			}
 		}
 	}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -18,7 +18,7 @@
 	function::{CallLocation, FuncDesc, FuncVal},
 	in_frame,
 	typed::Typed,
-	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk, ThunkValue},
+	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk},
 	Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
 	ResultExt, Unbound, Val,
 };
@@ -139,29 +139,14 @@
 					#[cfg(feature = "exp-preserve-order")]
 					false,
 				) {
-					#[derive(Trace)]
-					struct ObjectFieldThunk {
-						obj: ObjValue,
-						field: IStr,
-					}
-					impl ThunkValue for ObjectFieldThunk {
-						type Output = Val;
-
-						fn get(self: Box<Self>) -> Result<Self::Output> {
-							self.obj.get(self.field).transpose().expect(
-								"field exists, as field name was obtained from object.fields()",
-							)
-						}
-					}
-
 					let fctx = Pending::new();
 					let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint());
+					let obj = obj.clone();
 					let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
 						Thunk::evaluated(Val::string(field.clone())),
-						Thunk::new(ObjectFieldThunk {
-							field: field.clone(),
-							obj: obj.clone(),
-						}),
+						Thunk!(move || obj.get(field).transpose().expect(
+							"field exists, as field name was obtained from object.fields()",
+						)),
 					])));
 					destruct(var, value, fctx.clone(), &mut new_bindings)?;
 					let ctx = ctx
@@ -609,21 +594,8 @@
 			if items.is_empty() {
 				Val::Arr(ArrValue::empty())
 			} else if items.len() == 1 {
-				#[derive(Trace)]
-				struct ArrayElement {
-					ctx: Context,
-					item: LocExpr,
-				}
-				impl ThunkValue for ArrayElement {
-					type Output = Val;
-					fn get(self: Box<Self>) -> Result<Val> {
-						evaluate(self.ctx, &self.item)
-					}
-				}
-				Val::Arr(ArrValue::lazy(vec![Thunk::new(ArrayElement {
-					ctx,
-					item: items[0].clone(),
-				})]))
+				let item = items[0].clone();
+				Val::Arr(ArrValue::lazy(vec![Thunk!(move || evaluate(ctx, &item))]))
 			} else {
 				Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))
 			}
@@ -631,21 +603,8 @@
 		ArrComp(expr, comp_specs) => {
 			let mut out = Vec::new();
 			evaluate_comp(ctx, comp_specs, &mut |ctx| {
-				#[derive(Trace)]
-				struct EvaluateThunk {
-					ctx: Context,
-					expr: LocExpr,
-				}
-				impl ThunkValue for EvaluateThunk {
-					type Output = Val;
-					fn get(self: Box<Self>) -> Result<Val> {
-						evaluate(self.ctx, &self.expr)
-					}
-				}
-				out.push(Thunk::new(EvaluateThunk {
-					ctx,
-					expr: expr.clone(),
-				}));
+				let expr = expr.clone();
+				out.push(Thunk!(move || evaluate(ctx, &expr)));
 				Ok(())
 			})?;
 			Val::Arr(ArrValue::lazy(out))
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -90,7 +90,8 @@
 		let fctx = Context::new_future();
 		let mut defaults = GcHashMap::with_capacity(
 			params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()
-				- filled_named - filled_positionals,
+				- filled_named
+				- filled_positionals,
 		);
 
 		for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {
@@ -232,22 +233,6 @@
 /// Creates Context, which has all argument default values applied
 /// and with unbound values causing error to be returned
 pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {
-	#[derive(Trace)]
-	struct DependsOnUnbound(IStr, ParamsDesc);
-	impl ThunkValue for DependsOnUnbound {
-		type Output = Val;
-		fn get(self: Box<Self>) -> Result<Val> {
-			Err(FunctionParameterNotBoundInCall(
-				Some(self.0.clone()),
-				self.1
-					.iter()
-					.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))
-					.collect(),
-			)
-			.into())
-		}
-	}
-
 	let fctx = Context::new_future();
 
 	let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());
@@ -267,10 +252,18 @@
 		} else {
 			destruct(
 				&param.0,
-				Thunk::new(DependsOnUnbound(
-					param.0.name().unwrap_or_else(|| "<destruct>".into()),
-					params.clone(),
-				)),
+				{
+					let param_name = param.0.name().unwrap_or_else(|| "<destruct>".into());
+					let params = params.clone();
+					Thunk!(move || Err(FunctionParameterNotBoundInCall(
+						Some(param_name),
+						params
+							.iter()
+							.map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))
+							.collect(),
+					)
+					.into()))
+				},
 				fctx.clone(),
 				&mut bindings,
 			)?;
modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -158,3 +158,5 @@
 		Self::new()
 	}
 }
+
+pub fn assert_trace<T: Trace>(_v: &T) {}
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -20,7 +20,7 @@
 	in_frame,
 	operator::evaluate_add_op,
 	tb,
-	val::{ArrValue, ThunkValue},
+	val::ArrValue,
 	MaybeUnbound, Result, Thunk, Unbound, Val,
 };
 
@@ -444,45 +444,16 @@
 		})
 	}
 	pub fn get_lazy(&self, key: IStr) -> Option<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(self.key)?.expect("field exists"))
-			}
-		}
-
 		if !self.has_field_ex(key.clone(), true) {
 			return None;
 		}
-		Some(Thunk::new(ThunkGet {
-			obj: self.clone(),
-			key,
-		}))
+		let obj = self.clone();
+
+		Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))
 	}
 	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> {
-				self.obj.get_or_bail(self.key)
-			}
-		}
-
-		Thunk::new(ThunkGet {
-			obj: self.clone(),
-			key,
-		})
+		let obj = self.clone();
+		Thunk!(move || obj.get_or_bail(key))
 	}
 	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
 		Cc::ptr_eq(&a.0, &b.0)
@@ -733,11 +704,10 @@
 		self.value_cache
 			.borrow_mut()
 			.insert(cache_key.clone(), CacheValue::Pending);
-		let value = self.get_for_uncached(key, this).map_err(|e| {
+		let value = self.get_for_uncached(key, this).inspect_err(|e| {
 			self.value_cache
 				.borrow_mut()
 				.insert(cache_key.clone(), CacheValue::Errored(e.clone()));
-			e
 		})?;
 		self.value_cache.borrow_mut().insert(
 			cache_key,
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -11,6 +11,7 @@
 use derivative::Derivative;
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::Thunk;
 use jrsonnet_types::ValType;
 use thiserror::Error;
 
@@ -32,6 +33,27 @@
 }
 
 #[derive(Trace)]
+pub struct ThunkValueClosure<D: Trace, O: 'static> {
+	env: D,
+	// Carries no data, as it is not a real closure, all the
+	// captured environment is stored in `env` field.
+	#[trace(skip)]
+	closure: fn(D) -> Result<O>,
+}
+impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {
+	pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {
+		Self { env, closure }
+	}
+}
+impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {
+	type Output = O;
+
+	fn get(self: Box<Self>) -> Result<Self::Output> {
+		(self.closure)(self.env)
+	}
+}
+
+#[derive(Trace)]
 enum ThunkInner<T: Trace> {
 	Computed(T),
 	Errored(Error),
@@ -113,28 +135,11 @@
 		M: ThunkMapper<Input>,
 		M::Output: Trace,
 	{
-		#[derive(Trace)]
-		struct Mapped<Input: Trace, Mapper: Trace> {
-			inner: Thunk<Input>,
-			mapper: Mapper,
-		}
-		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>
-		where
-			Input: Trace + Clone,
-			Mapper: ThunkMapper<Input>,
-		{
-			type Output = Mapper::Output;
-
-			fn get(self: Box<Self>) -> Result<Self::Output> {
-				let value = self.inner.evaluate()?;
-				let mapped = self.mapper.map(value)?;
-				Ok(mapped)
-			}
-		}
-
-		Thunk::new(Mapped::<Input, M> {
-			inner: self,
-			mapper,
+		let inner = self;
+		Thunk!(move || {
+			let value = inner.evaluate()?;
+			let mapped = mapper.map(value)?;
+			Ok(mapped)
 		})
 	}
 }
modifiedcrates/jrsonnet-macros/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-macros/Cargo.toml
+++ b/crates/jrsonnet-macros/Cargo.toml
@@ -17,3 +17,4 @@
 proc-macro2.workspace = true
 quote.workspace = true
 syn = { workspace = true, features = ["full"] }
+syn-dissect-closure.workspace = true
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -1,7 +1,7 @@
 use std::string::String;
 
 use proc_macro2::TokenStream;
-use quote::quote;
+use quote::{quote, quote_spanned};
 use syn::{
 	parenthesized,
 	parse::{Parse, ParseStream},
@@ -9,8 +9,8 @@
 	punctuated::Punctuated,
 	spanned::Spanned,
 	token::{self, Comma},
-	Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
-	PathArguments, Result, ReturnType, Token, Type,
+	Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,
+	LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,
 };
 
 fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>
@@ -815,3 +815,30 @@
 	let input = parse_macro_input!(input as FormatInput);
 	input.expand().into()
 }
+
+/// Create Thunk using closure syntax
+#[proc_macro]
+#[allow(non_snake_case)]
+pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
+	let input = parse_macro_input!(input as ExprClosure);
+
+	let span = input.inputs.span();
+	let move_check = input.capture.is_none().then(|| {
+		quote_spanned! {span => {
+			compile_error!("Thunk! needs to be called with move closure");
+		}}
+	});
+
+	let (env, closure, args) = syn_dissect_closure::split_env(input);
+
+	let trace_check = args.iter().map(|el| {
+		let span = el.span();
+		quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}
+	});
+
+	quote! {{
+		#move_check
+		#(#trace_check)*
+		::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))
+	}}.into()
+}