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

difftreelog

feat unify Arg and Typed handling for Thunk

Yaroslav Bolyukin2023-07-27parent: #a85a211.patch.diff
in: master

8 files changed

modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -81,7 +81,7 @@
 	#[must_use]
 	pub fn into_future(self, ctx: Pending<Self>) -> Self {
 		{
-			ctx.0.borrow_mut().replace(self);
+			ctx.clone().fill(self);
 		}
 		ctx.unwrap()
 	}
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -1,29 +1,49 @@
-use std::cell::RefCell;
+use std::cell::OnceCell;
 
 use jrsonnet_gcmodule::{Cc, Trace};
 
+use crate::{error::ErrorKind::InfiniteRecursionDetected, throw, val::ThunkValue, Result, Thunk};
+
 // TODO: Replace with OnceCell once in std
 #[derive(Clone, Trace)]
-pub struct Pending<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
+pub struct Pending<V: Trace + 'static>(pub Cc<OnceCell<V>>);
 impl<T: Trace + 'static> Pending<T> {
 	pub fn new() -> Self {
-		Self(Cc::new(RefCell::new(None)))
+		Self(Cc::new(OnceCell::new()))
 	}
 	pub fn new_filled(v: T) -> Self {
-		Self(Cc::new(RefCell::new(Some(v))))
+		let cell = OnceCell::new();
+		let _ = cell.set(v);
+		Self(Cc::new(cell))
 	}
 	/// # Panics
 	/// If wrapper is filled already
 	pub fn fill(self, value: T) {
-		assert!(self.0.borrow().is_none(), "wrapper is filled already");
-		self.0.borrow_mut().replace(value);
+		self.0
+			.set(value)
+			.map_err(|_| ())
+			.expect("wrapper is filled already")
 	}
 }
 impl<T: Clone + Trace + 'static> Pending<T> {
 	/// # Panics
 	/// If wrapper is not yet filled
 	pub fn unwrap(&self) -> T {
-		self.0.borrow().as_ref().cloned().unwrap()
+		self.0.get().cloned().expect("pending was not filled")
+	}
+	pub fn try_get(&self) -> Option<T> {
+		self.0.get().cloned()
+	}
+}
+
+impl<T: Trace + Clone> ThunkValue for Pending<T> {
+	type Output = T;
+
+	fn get(self: Box<Self>) -> Result<Self::Output> {
+		let Some(value) = self.0.get() else {
+			throw!(InfiniteRecursionDetected);
+		};
+		Ok(value.clone())
 	}
 }
 
@@ -32,3 +52,9 @@
 		Self::new()
 	}
 }
+
+impl<T: Trace + Clone> Into<Thunk<T>> for Pending<T> {
+	fn into(self) -> Thunk<T> {
+		Thunk::new(self)
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/arglike.rs
1use hashbrown::HashMap;2use jrsonnet_gcmodule::Trace;3use jrsonnet_interner::IStr;4use jrsonnet_parser::{ArgsDesc, LocExpr};56use crate::{7	error::Result,8	evaluate,9	gc::GcHashMap,10	typed::Typed,11	val::{StrValue, ThunkValue},12	Context, Thunk, Val,13};1415/// Marker for arguments, which can be evaluated with context set to None16pub trait OptionalContext {}1718#[derive(Trace)]19struct EvaluateThunk {20	ctx: Context,21	expr: LocExpr,22}23impl ThunkValue for EvaluateThunk {24	type Output = Val;25	fn get(self: Box<Self>) -> Result<Val> {26		evaluate(self.ctx, &self.expr)27	}28}2930pub trait ArgLike {31	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>>;32}3334impl ArgLike for &LocExpr {35	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {36		Ok(if tailstrict {37			Thunk::evaluated(evaluate(ctx, self)?)38		} else {39			Thunk::new(EvaluateThunk {40				ctx,41				expr: (*self).clone(),42			})43		})44	}45}4647impl<T> ArgLike for T48where49	T: Typed + Clone,50{51	fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<Thunk<Val>> {52		let val = T::into_untyped(self.clone())?;53		Ok(Thunk::evaluated(val))54	}55}56impl<T> OptionalContext for T where T: Typed + Clone {}5758impl ArgLike for Thunk<Val> {59	fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {60		if tailstrict {61			self.force()?;62		}63		Ok(self.clone())64	}65}66impl OptionalContext for Thunk<Val> {}6768#[derive(Clone, Trace)]69pub enum TlaArg {70	String(IStr),71	Code(LocExpr),72	Val(Val),73}74impl ArgLike for TlaArg {75	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {76		match self {77			TlaArg::String(s) => Ok(Thunk::evaluated(Val::Str(StrValue::Flat(s.clone())))),78			TlaArg::Code(code) => Ok(if tailstrict {79				Thunk::evaluated(evaluate(ctx, code)?)80			} else {81				Thunk::new(EvaluateThunk {82					ctx,83					expr: code.clone(),84				})85			}),86			TlaArg::Val(val) => Ok(Thunk::evaluated(val.clone())),87		}88	}89}9091mod sealed {92	/// Implemented for `ArgsLike`, where only unnamed arguments present93	pub trait Unnamed {}94	/// Implemented for `ArgsLike`, where only named arguments present95	pub trait Named {}96}9798pub trait ArgsLike {99	fn unnamed_len(&self) -> usize;100	fn unnamed_iter(101		&self,102		ctx: Context,103		tailstrict: bool,104		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,105	) -> Result<()>;106	fn named_iter(107		&self,108		ctx: Context,109		tailstrict: bool,110		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,111	) -> Result<()>;112	fn named_names(&self, handler: &mut dyn FnMut(&IStr));113}114115impl ArgsLike for Vec<Val> {116	fn unnamed_len(&self) -> usize {117		self.len()118	}119	fn unnamed_iter(120		&self,121		_ctx: Context,122		_tailstrict: bool,123		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,124	) -> Result<()> {125		for (idx, el) in self.iter().enumerate() {126			handler(idx, Thunk::evaluated(el.clone()))?;127		}128		Ok(())129	}130	fn named_iter(131		&self,132		_ctx: Context,133		_tailstrict: bool,134		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,135	) -> Result<()> {136		Ok(())137	}138	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}139}140141impl ArgsLike for ArgsDesc {142	fn unnamed_len(&self) -> usize {143		self.unnamed.len()144	}145146	fn unnamed_iter(147		&self,148		ctx: Context,149		tailstrict: bool,150		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,151	) -> Result<()> {152		for (id, arg) in self.unnamed.iter().enumerate() {153			handler(154				id,155				if tailstrict {156					Thunk::evaluated(evaluate(ctx.clone(), arg)?)157				} else {158					Thunk::new(EvaluateThunk {159						ctx: ctx.clone(),160						expr: arg.clone(),161					})162				},163			)?;164		}165		Ok(())166	}167168	fn named_iter(169		&self,170		ctx: Context,171		tailstrict: bool,172		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,173	) -> Result<()> {174		for (name, arg) in &self.named {175			handler(176				name,177				if tailstrict {178					Thunk::evaluated(evaluate(ctx.clone(), arg)?)179				} else {180					Thunk::new(EvaluateThunk {181						ctx: ctx.clone(),182						expr: arg.clone(),183					})184				},185			)?;186		}187		Ok(())188	}189190	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {191		for (name, _) in &self.named {192			handler(name);193		}194	}195}196197impl<V: ArgLike, S> sealed::Named for HashMap<IStr, V, S> {}198impl<V: ArgLike, S> ArgsLike for HashMap<IStr, V, S> {199	fn unnamed_len(&self) -> usize {200		0201	}202203	fn unnamed_iter(204		&self,205		_ctx: Context,206		_tailstrict: bool,207		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,208	) -> Result<()> {209		Ok(())210	}211212	fn named_iter(213		&self,214		ctx: Context,215		tailstrict: bool,216		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,217	) -> Result<()> {218		for (name, value) in self.iter() {219			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;220		}221		Ok(())222	}223224	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {225		for (name, _) in self.iter() {226			handler(name);227		}228	}229}230impl<V, S> OptionalContext for HashMap<IStr, V, S> where V: ArgLike + OptionalContext {}231232impl<A: ArgLike> ArgsLike for GcHashMap<IStr, A> {233	fn unnamed_len(&self) -> usize {234		self.0.unnamed_len()235	}236237	fn unnamed_iter(238		&self,239		ctx: Context,240		tailstrict: bool,241		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,242	) -> Result<()> {243		self.0.unnamed_iter(ctx, tailstrict, handler)244	}245246	fn named_iter(247		&self,248		ctx: Context,249		tailstrict: bool,250		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,251	) -> Result<()> {252		self.0.named_iter(ctx, tailstrict, handler)253	}254255	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {256		self.0.named_names(handler);257	}258}259260macro_rules! impl_args_like {261	($count:expr; $($gen:ident)*) => {262		impl<$($gen: ArgLike,)*> sealed::Unnamed for ($($gen,)*) {}263		impl<$($gen: ArgLike,)*> ArgsLike for ($($gen,)*) {264			fn unnamed_len(&self) -> usize {265				$count266			}267			#[allow(non_snake_case, unused_assignments)]268			fn unnamed_iter(269				&self,270				ctx: Context,271				tailstrict: bool,272				handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,273			) -> Result<()> {274				let mut i = 0usize;275				let ($($gen,)*) = self;276				$(277					handler(i, $gen.evaluate_arg(ctx.clone(), tailstrict)?)?;278					i+=1;279				)*280				Ok(())281			}282			fn named_iter(283				&self,284				_ctx: Context,285				_tailstrict: bool,286				_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,287			) -> Result<()> {288				Ok(())289			}290			fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}291		}292		impl<$($gen: ArgLike,)*> OptionalContext for ($($gen,)*) where $($gen: OptionalContext),* {}293294		impl<$($gen: ArgLike,)*> sealed::Named for ($((IStr, $gen),)*) {}295		impl<$($gen: ArgLike,)*> ArgsLike for ($((IStr, $gen),)*) {296			fn unnamed_len(&self) -> usize {297				0298			}299			fn unnamed_iter(300				&self,301				_ctx: Context,302				_tailstrict: bool,303				_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,304			) -> Result<()> {305				Ok(())306			}307			#[allow(non_snake_case)]308			fn named_iter(309				&self,310				ctx: Context,311				tailstrict: bool,312				handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,313			) -> Result<()> {314				let ($($gen,)*) = self;315				$(316					handler(&$gen.0, $gen.1.evaluate_arg(ctx.clone(), tailstrict)?)?;317				)*318				Ok(())319			}320			#[allow(non_snake_case)]321			fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {322				let ($($gen,)*) = self;323				$(324					handler(&$gen.0);325				)*326			}327		}328		impl<$($gen: ArgLike,)*> OptionalContext for ($((IStr, $gen),)*) where $($gen: OptionalContext),* {}329	};330	($count:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {331		impl_args_like!($count; $($cur)*);332		impl_args_like!($count + 1usize; $($cur)* $c @ $($rest)*);333	};334	($count:expr; $($cur:ident)* @) => {335		impl_args_like!($count; $($cur)*);336	}337}338impl_args_like! {339	// First argument is already in position, so count starts from 1340	1usize; A @ B C D E F G H I J K L341}342343impl ArgsLike for () {344	fn unnamed_len(&self) -> usize {345		0346	}347348	fn unnamed_iter(349		&self,350		_ctx: Context,351		_tailstrict: bool,352		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,353	) -> Result<()> {354		Ok(())355	}356357	fn named_iter(358		&self,359		_ctx: Context,360		_tailstrict: bool,361		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,362	) -> Result<()> {363		Ok(())364	}365366	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}367}368impl OptionalContext for () {}
after · crates/jrsonnet-evaluator/src/function/arglike.rs
1use hashbrown::HashMap;2use jrsonnet_gcmodule::Trace;3use jrsonnet_interner::IStr;4use jrsonnet_parser::{ArgsDesc, LocExpr};56use crate::{7	error::Result,8	evaluate,9	gc::GcHashMap,10	typed::Typed,11	val::{StrValue, ThunkValue},12	Context, Thunk, Val,13};1415/// Marker for arguments, which can be evaluated with context set to None16pub trait OptionalContext {}1718#[derive(Trace)]19struct EvaluateThunk {20	ctx: Context,21	expr: LocExpr,22}23impl ThunkValue for EvaluateThunk {24	type Output = Val;25	fn get(self: Box<Self>) -> Result<Val> {26		evaluate(self.ctx, &self.expr)27	}28}2930pub trait ArgLike {31	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>>;32}3334impl ArgLike for &LocExpr {35	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {36		Ok(if tailstrict {37			Thunk::evaluated(evaluate(ctx, self)?)38		} else {39			Thunk::new(EvaluateThunk {40				ctx,41				expr: (*self).clone(),42			})43		})44	}45}4647impl<T> ArgLike for T48where49	T: Typed + Clone,50{51	fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {52		if T::provides_lazy() && !tailstrict {53			return Ok(T::into_lazy_untyped(self.clone()));54		}55		let val = T::into_untyped(self.clone())?;56		Ok(Thunk::evaluated(val))57	}58}59impl<T> OptionalContext for T where T: Typed + Clone {}6061#[derive(Clone, Trace)]62pub enum TlaArg {63	String(IStr),64	Code(LocExpr),65	Val(Val),66	Lazy(Thunk<Val>),67}68impl ArgLike for TlaArg {69	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {70		match self {71			TlaArg::String(s) => Ok(Thunk::evaluated(Val::Str(StrValue::Flat(s.clone())))),72			TlaArg::Code(code) => Ok(if tailstrict {73				Thunk::evaluated(evaluate(ctx, code)?)74			} else {75				Thunk::new(EvaluateThunk {76					ctx,77					expr: code.clone(),78				})79			}),80			TlaArg::Val(val) => Ok(Thunk::evaluated(val.clone())),81			TlaArg::Lazy(lazy) => Ok(lazy.clone()),82		}83	}84}8586mod sealed {87	/// Implemented for `ArgsLike`, where only unnamed arguments present88	pub trait Unnamed {}89	/// Implemented for `ArgsLike`, where only named arguments present90	pub trait Named {}91}9293pub trait ArgsLike {94	fn unnamed_len(&self) -> usize;95	fn unnamed_iter(96		&self,97		ctx: Context,98		tailstrict: bool,99		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,100	) -> Result<()>;101	fn named_iter(102		&self,103		ctx: Context,104		tailstrict: bool,105		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,106	) -> Result<()>;107	fn named_names(&self, handler: &mut dyn FnMut(&IStr));108}109110impl ArgsLike for Vec<Val> {111	fn unnamed_len(&self) -> usize {112		self.len()113	}114	fn unnamed_iter(115		&self,116		_ctx: Context,117		_tailstrict: bool,118		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,119	) -> Result<()> {120		for (idx, el) in self.iter().enumerate() {121			handler(idx, Thunk::evaluated(el.clone()))?;122		}123		Ok(())124	}125	fn named_iter(126		&self,127		_ctx: Context,128		_tailstrict: bool,129		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,130	) -> Result<()> {131		Ok(())132	}133	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}134}135136impl ArgsLike for ArgsDesc {137	fn unnamed_len(&self) -> usize {138		self.unnamed.len()139	}140141	fn unnamed_iter(142		&self,143		ctx: Context,144		tailstrict: bool,145		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,146	) -> Result<()> {147		for (id, arg) in self.unnamed.iter().enumerate() {148			handler(149				id,150				if tailstrict {151					Thunk::evaluated(evaluate(ctx.clone(), arg)?)152				} else {153					Thunk::new(EvaluateThunk {154						ctx: ctx.clone(),155						expr: arg.clone(),156					})157				},158			)?;159		}160		Ok(())161	}162163	fn named_iter(164		&self,165		ctx: Context,166		tailstrict: bool,167		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,168	) -> Result<()> {169		for (name, arg) in &self.named {170			handler(171				name,172				if tailstrict {173					Thunk::evaluated(evaluate(ctx.clone(), arg)?)174				} else {175					Thunk::new(EvaluateThunk {176						ctx: ctx.clone(),177						expr: arg.clone(),178					})179				},180			)?;181		}182		Ok(())183	}184185	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {186		for (name, _) in &self.named {187			handler(name);188		}189	}190}191192impl<V: ArgLike, S> sealed::Named for HashMap<IStr, V, S> {}193impl<V: ArgLike, S> ArgsLike for HashMap<IStr, V, S> {194	fn unnamed_len(&self) -> usize {195		0196	}197198	fn unnamed_iter(199		&self,200		_ctx: Context,201		_tailstrict: bool,202		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,203	) -> Result<()> {204		Ok(())205	}206207	fn named_iter(208		&self,209		ctx: Context,210		tailstrict: bool,211		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,212	) -> Result<()> {213		for (name, value) in self.iter() {214			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;215		}216		Ok(())217	}218219	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {220		for (name, _) in self.iter() {221			handler(name);222		}223	}224}225impl<V, S> OptionalContext for HashMap<IStr, V, S> where V: ArgLike + OptionalContext {}226227impl<A: ArgLike> ArgsLike for GcHashMap<IStr, A> {228	fn unnamed_len(&self) -> usize {229		self.0.unnamed_len()230	}231232	fn unnamed_iter(233		&self,234		ctx: Context,235		tailstrict: bool,236		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,237	) -> Result<()> {238		self.0.unnamed_iter(ctx, tailstrict, handler)239	}240241	fn named_iter(242		&self,243		ctx: Context,244		tailstrict: bool,245		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,246	) -> Result<()> {247		self.0.named_iter(ctx, tailstrict, handler)248	}249250	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {251		self.0.named_names(handler);252	}253}254255macro_rules! impl_args_like {256	($count:expr; $($gen:ident)*) => {257		impl<$($gen: ArgLike,)*> sealed::Unnamed for ($($gen,)*) {}258		impl<$($gen: ArgLike,)*> ArgsLike for ($($gen,)*) {259			fn unnamed_len(&self) -> usize {260				$count261			}262			#[allow(non_snake_case, unused_assignments)]263			fn unnamed_iter(264				&self,265				ctx: Context,266				tailstrict: bool,267				handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,268			) -> Result<()> {269				let mut i = 0usize;270				let ($($gen,)*) = self;271				$(272					handler(i, $gen.evaluate_arg(ctx.clone(), tailstrict)?)?;273					i+=1;274				)*275				Ok(())276			}277			fn named_iter(278				&self,279				_ctx: Context,280				_tailstrict: bool,281				_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,282			) -> Result<()> {283				Ok(())284			}285			fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}286		}287		impl<$($gen: ArgLike,)*> OptionalContext for ($($gen,)*) where $($gen: OptionalContext),* {}288289		impl<$($gen: ArgLike,)*> sealed::Named for ($((IStr, $gen),)*) {}290		impl<$($gen: ArgLike,)*> ArgsLike for ($((IStr, $gen),)*) {291			fn unnamed_len(&self) -> usize {292				0293			}294			fn unnamed_iter(295				&self,296				_ctx: Context,297				_tailstrict: bool,298				_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,299			) -> Result<()> {300				Ok(())301			}302			#[allow(non_snake_case)]303			fn named_iter(304				&self,305				ctx: Context,306				tailstrict: bool,307				handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,308			) -> Result<()> {309				let ($($gen,)*) = self;310				$(311					handler(&$gen.0, $gen.1.evaluate_arg(ctx.clone(), tailstrict)?)?;312				)*313				Ok(())314			}315			#[allow(non_snake_case)]316			fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {317				let ($($gen,)*) = self;318				$(319					handler(&$gen.0);320				)*321			}322		}323		impl<$($gen: ArgLike,)*> OptionalContext for ($((IStr, $gen),)*) where $($gen: OptionalContext),* {}324	};325	($count:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {326		impl_args_like!($count; $($cur)*);327		impl_args_like!($count + 1usize; $($cur)* $c @ $($rest)*);328	};329	($count:expr; $($cur:ident)* @) => {330		impl_args_like!($count; $($cur)*);331	}332}333impl_args_like! {334	// First argument is already in position, so count starts from 1335	1usize; A @ B C D E F G H I J K L336}337338impl ArgsLike for () {339	fn unnamed_len(&self) -> usize {340		0341	}342343	fn unnamed_iter(344		&self,345		_ctx: Context,346		_tailstrict: bool,347		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,348	) -> Result<()> {349		Ok(())350	}351352	fn named_iter(353		&self,354		_ctx: Context,355		_tailstrict: bool,356		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,357	) -> Result<()> {358		Ok(())359	}360361	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}362}363impl OptionalContext for () {}
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -15,7 +15,9 @@
 	function::CallLocation,
 	gc::{GcHashMap, GcHashSet, TraceBox},
 	operator::evaluate_add_op,
-	tb, throw, MaybeUnbound, Result, State, Thunk, Unbound, Val,
+	tb, throw,
+	val::ThunkValue,
+	MaybeUnbound, Result, State, Thunk, Unbound, Val,
 };
 
 #[cfg(not(feature = "exp-preserve-order"))]
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -1,6 +1,6 @@
-use std::ops::Deref;
+use std::{collections::BTreeMap, marker::PhantomData, ops::Deref};
 
-use jrsonnet_gcmodule::Cc;
+use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::{IBytes, IStr};
 pub use jrsonnet_macros::Typed;
 use jrsonnet_types::{ComplexValType, ValType};
@@ -11,10 +11,28 @@
 	function::{native::NativeDesc, FuncDesc, FuncVal},
 	throw,
 	typed::CheckType,
-	val::{IndexableVal, StrValue},
-	ObjValue, ObjValueBuilder, Val,
+	val::{IndexableVal, StrValue, ThunkMapper},
+	ObjValue, ObjValueBuilder, Thunk, Val,
 };
 
+#[derive(Trace)]
+struct FromUntyped<K: Trace>(PhantomData<fn() -> K>);
+impl<K> ThunkMapper<Val> for FromUntyped<K>
+where
+	K: Typed + Trace,
+{
+	type Output = K;
+
+	fn map(self, from: Val) -> Result<Self::Output> {
+		K::from_untyped(from)
+	}
+}
+impl<K: Trace> Default for FromUntyped<K> {
+	fn default() -> Self {
+		Self(PhantomData)
+	}
+}
+
 pub trait TypedObj: Typed {
 	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
 	fn parse(obj: &ObjValue) -> Result<Self>;
@@ -28,8 +46,24 @@
 pub trait Typed: Sized {
 	const TYPE: &'static ComplexValType;
 	fn into_untyped(typed: Self) -> Result<Val>;
+	fn into_lazy_untyped(typed: Self) -> Thunk<Val> {
+		Thunk::from(Self::into_untyped(typed))
+	}
 	fn from_untyped(untyped: Val) -> Result<Self>;
+	fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {
+		Self::from_untyped(lazy.evaluate()?)
+	}
+
+	// Whatever caller should use `into_lazy_untyped` instead of `into_untyped`
+	fn provides_lazy() -> bool {
+		false
+	}
 
+	// Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible
+	fn wants_lazy() -> bool {
+		false
+	}
+
 	/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result
 	/// This method returns identity in impl Typed for Result, and should not be overriden
 	#[doc(hidden)]
@@ -39,6 +73,54 @@
 	}
 }
 
+impl<T> Typed for Thunk<T>
+where
+	T: Typed + Trace + Clone,
+{
+	const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);
+
+	fn into_untyped(typed: Self) -> Result<Val> {
+		T::into_untyped(typed.evaluate()?)
+	}
+
+	fn from_untyped(untyped: Val) -> Result<Self> {
+		Self::from_lazy_untyped(Thunk::evaluated(untyped))
+	}
+
+	fn provides_lazy() -> bool {
+		true
+	}
+
+	fn into_lazy_untyped(inner: Self) -> Thunk<Val> {
+		#[derive(Trace)]
+		struct IntoUntyped<K: Trace>(PhantomData<fn() -> K>);
+		impl<K> ThunkMapper<K> for IntoUntyped<K>
+		where
+			K: Typed + Trace,
+		{
+			type Output = Val;
+
+			fn map(self, from: K) -> Result<Self::Output> {
+				K::into_untyped(from)
+			}
+		}
+		impl<K: Trace> Default for IntoUntyped<K> {
+			fn default() -> Self {
+				Self(PhantomData)
+			}
+		}
+		inner.map(<IntoUntyped<T>>::default())
+	}
+
+	fn wants_lazy() -> bool {
+		true
+	}
+
+	fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {
+		Ok(inner.map(<FromUntyped<T>>::default()))
+	}
+}
+
 const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;
 
 macro_rules! impl_int {
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -252,6 +252,7 @@
 				}
 				Ok(())
 			}
+			Self::Lazy(_lazy) => Ok(()),
 		}
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -88,6 +88,54 @@
 	}
 }
 
+pub trait ThunkMapper<Input>: Trace {
+	type Output;
+	fn map(self, from: Input) -> Result<Self::Output>;
+}
+impl<Input> Thunk<Input>
+where
+	Input: Trace + Clone,
+{
+	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>
+	where
+		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,
+		})
+	}
+}
+
+impl<T: Trace> From<Result<T>> for Thunk<T> {
+	fn from(value: Result<T>) -> Self {
+		match value {
+			Ok(o) => Self::evaluated(o),
+			Err(e) => Self::errored(e),
+		}
+	}
+}
+
 type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);
 
 #[derive(Trace, Clone)]
@@ -272,6 +320,11 @@
 		Self::Flat(value.into())
 	}
 }
+impl From<IStr> for StrValue {
+	fn from(value: IStr) -> Self {
+		Self::Flat(value)
+	}
+}
 impl Display for StrValue {
 	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 		match self {
modifiedcrates/jrsonnet-types/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -128,10 +128,12 @@
 	Array(Box<ComplexValType>),
 	ArrayRef(&'static ComplexValType),
 	ObjectRef(&'static [(&'static str, &'static ComplexValType)]),
+	AttrsOf(&'static ComplexValType),
 	Union(Vec<ComplexValType>),
 	UnionRef(&'static [&'static ComplexValType]),
 	Sum(Vec<ComplexValType>),
 	SumRef(&'static [&'static ComplexValType]),
+	Lazy(&'static ComplexValType),
 }
 
 impl From<ValType> for ComplexValType {
@@ -195,10 +197,18 @@
 				}
 				write!(f, "}}")?;
 			}
+			ComplexValType::AttrsOf(a) => {
+				if matches!(a, ComplexValType::Any) {
+					write!(f, "object")?;
+				} else {
+					write!(f, "AttrsOf<{a}>")?;
+				}
+			}
 			ComplexValType::Union(v) => write_union(f, true, v.iter())?,
 			ComplexValType::UnionRef(v) => write_union(f, true, v.iter().copied())?,
 			ComplexValType::Sum(v) => write_union(f, false, v.iter())?,
 			ComplexValType::SumRef(v) => write_union(f, false, v.iter().copied())?,
+			ComplexValType::Lazy(lazy) => write!(f, "Lazy<{lazy}>")?,
 		};
 		Ok(())
 	}