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

difftreelog

refactor trivial arrays

pynwztymYaroslav Bolyukin2026-05-08parent: #b89fdd3.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -138,14 +138,12 @@
 #[derive(Debug, Acyclic)]
 pub enum LExpr {
 	Slot(LSlot),
-	Null,
-	Bool(bool),
-	Str(IStr),
-	Num(NumValue),
+	Trivial(TrivialVal),
 	Arr {
 		shape: ClosureShape,
 		items: Rc<Vec<LExpr>>,
 	},
+	ArrConst(Rc<Vec<TrivialVal>>),
 	ArrComp(Box<LArrComp>),
 	Obj(LObjBody),
 	ObjExtend(Box<LExpr>, LObjBody),
@@ -1345,15 +1343,15 @@
 #[allow(clippy::too_many_lines)]
 pub fn analyze(expr: &Expr, stack: &mut AnalysisStack, taint: &mut AnalysisResult) -> LExpr {
 	match expr {
-		Expr::Literal(span, l) => match l {
-			LiteralType::This => stack.use_this(taint).map_or_else(
+		Expr::Identity(span, l) => match l {
+			IdentityKind::This => stack.use_this(taint).map_or_else(
 				|| {
 					stack.report_error("`self` used outside of object", Some(span.clone()));
 					LExpr::BadLocal("self")
 				},
 				LExpr::Slot,
 			),
-			LiteralType::Super => {
+			IdentityKind::Super => {
 				if stack.use_super(taint).is_some() {
 					LExpr::Super
 				} else {
@@ -1361,25 +1359,34 @@
 					LExpr::BadLocal("super")
 				}
 			}
-			LiteralType::Dollar => stack.use_dollar(taint).map_or_else(
+			IdentityKind::Dollar => stack.use_dollar(taint).map_or_else(
 				|| {
 					stack.report_error("`$` used outside of object", Some(span.clone()));
 					LExpr::BadLocal("$")
 				},
 				LExpr::Slot,
 			),
-			LiteralType::Null => LExpr::Null,
-			LiteralType::True => LExpr::Bool(true),
-			LiteralType::False => LExpr::Bool(false),
 		},
-		Expr::Str(s) => LExpr::Str(s.clone()),
-		Expr::Num(n) => LExpr::Num(*n),
+		Expr::Trivial(tv) => LExpr::Trivial(tv.clone()),
 		Expr::Var(v) => stack
 			.use_local(&v.value, v.span.clone(), taint)
 			.map_or_else(|| LExpr::BadLocal("ref"), LExpr::Slot),
 		Expr::Arr(a) => {
-			let (shape, items) = stack
-				.in_using_closure(|stack| a.iter().map(|v| analyze(v, stack, taint)).collect());
+			if a.iter().all(|i| matches!(i, Expr::Trivial(_))) {
+				let trivials: Vec<_> = a
+					.iter()
+					.map(|i| match i {
+						Expr::Trivial(tv) => tv.clone(),
+						_ => unreachable!("checked above"),
+					})
+					.collect();
+				return LExpr::ArrConst(Rc::new(trivials));
+			}
+			let (shape, items) = stack.in_using_closure(|stack| {
+				a.iter()
+					.map(|v| analyze(v, stack, taint))
+					.collect::<Vec<_>>()
+			});
 			LExpr::Arr {
 				shape,
 				items: Rc::new(items),
@@ -1412,7 +1419,7 @@
 		}
 		Expr::LocalExpr(binds, body) => analyze_local_expr(binds, body, stack, taint),
 		Expr::Import(kind, path_expr) => {
-			let Expr::Str(path) = &**path_expr else {
+			let Expr::Trivial(TrivialVal::Str(path)) = &**path_expr else {
 				stack.report_error(
 					"import path must be a string literal",
 					Some(kind.span.clone()),
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -8,12 +8,7 @@
 
 use jrsonnet_gcmodule::{Cc, cc_dyn};
 
-use crate::{
-	Context, Result, Thunk, Val,
-	analyze::{ClosureShape, LExpr},
-	function::NativeFn,
-	typed::IntoUntyped,
-};
+use crate::{Context, Result, Thunk, Val, analyze::LExpr, function::NativeFn, typed::IntoUntyped};
 
 mod spec;
 pub use spec::{ArrayLike, *};
@@ -42,8 +37,8 @@
 		Self::new(())
 	}
 
-	pub fn expr(ctx: Context, shape: &ClosureShape, exprs: Rc<Vec<LExpr>>) -> Self {
-		Self::new(ExprArray::new(ctx, shape, exprs))
+	pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {
+		Self::new(ExprArray::new(ctx, exprs))
 	}
 
 	pub fn repeated(data: Self, repeats: u32) -> Option<Self> {
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
after · crates/jrsonnet-evaluator/src/arr/spec.rs
1use std::{2	any::Any,3	cell::RefCell,4	fmt::{self, Debug},5	mem::replace,6	rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::{IBytes, IStr};11use jrsonnet_ir::TrivialVal;1213use super::{ArrValue, arridx};14use crate::{15	Context, Error, ObjValue, Result, Thunk, Val,16	analyze::LExpr,17	error::ErrorKind::InfiniteRecursionDetected,18	evaluate::evaluate,19	function::NativeFn,20	typed::{IntoUntyped, Typed},21	val::ThunkValue,22};2324pub trait ArrayLike: Any + Trace + Debug {25	fn len32(&self) -> u32;26	fn is_empty(&self) -> bool {27		self.len32() == 028	}29	fn get32(&self, index: u32) -> Result<Option<Val>>;30	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>>;3132	fn is_cheap(&self) -> bool {33		false34	}35}36trait ArrayCheap {37	fn get(&self, index: u32) -> Option<Val>;38	fn len(&self) -> u32;39}40impl<T> ArrayLike for T41where42	T: Any + Trace + Debug + ArrayCheap,43{44	fn len32(&self) -> u32 {45		<T as ArrayCheap>::len(self)46	}4748	fn get32(&self, index: u32) -> Result<Option<Val>> {49		Ok(<T as ArrayCheap>::get(self, index))50	}5152	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {53		<T as ArrayCheap>::get(self, index).map(Thunk::evaluated)54	}5556	fn is_cheap(&self) -> bool {57		true58	}59}6061impl ArrayCheap for () {62	fn len(&self) -> u32 {63		064	}65	fn get(&self, _index: u32) -> Option<Val> {66		None67	}68}6970#[derive(Debug, Trace)]71pub struct SliceArray {72	pub(crate) inner: ArrValue,73	pub(crate) from: u32,74	pub(crate) to: u32,75	pub(crate) step: u32,76}7778impl SliceArray {79	fn map_idx(&self, index: u32) -> u32 {80		self.from + self.step * index81	}82}83impl ArrayLike for SliceArray {84	fn len32(&self) -> u32 {85		(self.to - self.from).div_ceil(self.step)86	}8788	fn get32(&self, index: u32) -> Result<Option<Val>> {89		self.inner.get32(self.map_idx(index))90	}9192	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {93		self.inner.get_lazy32(self.map_idx(index))94	}9596	fn is_cheap(&self) -> bool {97		self.inner.is_cheap()98	}99}100101impl ArrayCheap for IBytes {102	fn len(&self) -> u32 {103		arridx(self.as_slice().len())104	}105	fn get(&self, index: u32) -> Option<Val> {106		self.as_slice()107			.get(index as usize)108			.map(|v| Val::Num((*v).into()))109	}110}111112impl ArrayCheap for Rc<Vec<TrivialVal>> {113	fn get(&self, index: u32) -> Option<Val> {114		self.as_slice()115			.get(index as usize)116			.map(|tv| tv.clone().into())117	}118119	fn len(&self) -> u32 {120		arridx(self.as_slice().len())121	}122}123124#[derive(Debug, Trace, Clone)]125enum ArrayThunk {126	Computed(Val),127	Errored(Error),128	Waiting,129	Pending,130}131132#[derive(Debug, Trace, Clone)]133pub struct ExprArray {134	ctx: Context,135	src: Rc<Vec<LExpr>>,136	cached: Cc<RefCell<Vec<ArrayThunk>>>,137}138impl ExprArray {139	pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {140		Self {141			ctx,142			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),143			src,144		}145	}146}147impl ArrayLike for ExprArray {148	fn len32(&self) -> u32 {149		arridx(self.cached.borrow().len())150	}151	fn get32(&self, index: u32) -> Result<Option<Val>> {152		if index >= self.len32() {153			return Ok(None);154		}155		match &self.cached.borrow()[index as usize] {156			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),157			ArrayThunk::Errored(e) => return Err(e.clone()),158			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),159			ArrayThunk::Waiting => {}160		}161162		let ArrayThunk::Waiting = replace(163			&mut self.cached.borrow_mut()[index as usize],164			ArrayThunk::Pending,165		) else {166			unreachable!()167		};168169		let result = evaluate(self.ctx.clone(), &self.src[index as usize]);170		match result {171			Ok(new_value) => {172				self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());173				Ok(Some(new_value))174			}175			Err(e) => {176				self.cached.borrow_mut()[index as usize] = ArrayThunk::Waiting;177				Err(e)178			}179		}180	}181	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {182		#[derive(Trace)]183		struct ExprArrThunk {184			expr: ExprArray,185			index: u32,186		}187		impl ThunkValue for ExprArrThunk {188			type Output = Val;189190			fn get(&self) -> Result<Self::Output> {191				self.expr192					.get32(self.index)193					.transpose()194					.expect("index checked")195			}196		}197198		if index >= self.len32() {199			return None;200		}201		match &self.cached.borrow()[index as usize] {202			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),203			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),204			ArrayThunk::Waiting | ArrayThunk::Pending => {}205		}206207		Some(Thunk::new(ExprArrThunk {208			expr: self.clone(),209			index,210		}))211	}212	fn is_cheap(&self) -> bool {213		false214	}215}216217#[derive(Trace, Debug)]218pub struct ExtendedArray {219	pub a: ArrValue,220	pub b: ArrValue,221	split: u32,222	len: u32,223}224impl ExtendedArray {225	pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {226		let a_len = a.len32();227		let b_len = b.len32();228		let len = a_len.checked_add(b_len)?;229		Some(Self {230			a,231			b,232			split: a_len,233			len,234		})235	}236}237238struct WithExactSize<I>(I, usize);239impl<I, T> Iterator for WithExactSize<I>240where241	I: Iterator<Item = T>,242{243	type Item = T;244245	fn next(&mut self) -> Option<Self::Item> {246		self.0.next()247	}248	fn nth(&mut self, n: usize) -> Option<Self::Item> {249		self.0.nth(n)250	}251	fn size_hint(&self) -> (usize, Option<usize>) {252		(self.1, Some(self.1))253	}254}255impl<I> DoubleEndedIterator for WithExactSize<I>256where257	I: DoubleEndedIterator,258{259	fn next_back(&mut self) -> Option<Self::Item> {260		self.0.next_back()261	}262	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {263		self.0.nth_back(n)264	}265}266impl<I> ExactSizeIterator for WithExactSize<I>267where268	I: Iterator,269{270	fn len(&self) -> usize {271		self.1272	}273}274impl ArrayLike for ExtendedArray {275	fn get32(&self, index: u32) -> Result<Option<Val>> {276		if self.split > index {277			self.a.get32(index)278		} else {279			self.b.get32(index - self.split)280		}281	}282	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {283		if self.split > index {284			self.a.get_lazy32(index)285		} else {286			self.b.get_lazy32(index - self.split)287		}288	}289290	fn len32(&self) -> u32 {291		self.len292	}293294	fn is_cheap(&self) -> bool {295		self.a.is_cheap() && self.b.is_cheap()296	}297}298299impl<T> ArrayLike for Vec<T>300where301	T: IntoUntyped + Trace + fmt::Debug,302	for<'a> &'a T: IntoUntyped,303{304	fn len32(&self) -> u32 {305		self.as_slice().len().try_into().unwrap_or(u32::MAX)306	}307308	fn get32(&self, index: u32) -> Result<Option<Val>> {309		let Some(elem) = self.as_slice().get(index as usize) else {310			return Ok(None);311		};312		IntoUntyped::into_untyped(elem).map(Some)313	}314315	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {316		let elem = self.as_slice().get(index as usize)?;317		Some(IntoUntyped::into_lazy_untyped(elem))318	}319320	fn is_cheap(&self) -> bool {321		!T::provides_lazy()322	}323}324325/// Inclusive range type326#[derive(Debug, Trace, PartialEq, Eq)]327pub struct RangeArray {328	start: i32,329	end: i32,330}331impl RangeArray {332	pub fn empty() -> Self {333		Self::new_exclusive(0, 0)334	}335	pub fn new_exclusive(start: i32, end: i32) -> Self {336		end.checked_sub(1)337			.map_or_else(Self::empty, |end| Self { start, end })338	}339	pub fn new_inclusive(start: i32, end: i32) -> Self {340		Self { start, end }341	}342	#[expect(343		clippy::cast_sign_loss,344		reason = "the math is valid with wrapping, sign loss works as intended"345	)]346	fn size(&self) -> u32 {347		(self.end as u32)348			.wrapping_sub(self.start as u32)349			.wrapping_add(1)350	}351	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {352		WithExactSize(self.start..=self.end, self.size() as usize)353	}354}355impl ArrayCheap for RangeArray {356	fn get(&self, index: u32) -> Option<Val> {357		self.range().nth(index as usize).map(|i| Val::Num(i.into()))358	}359	fn len(&self) -> u32 {360		self.size()361	}362}363364#[derive(Debug, Trace)]365pub struct ReverseArray(pub ArrValue);366impl ArrayLike for ReverseArray {367	fn len32(&self) -> u32 {368		self.0.len32()369	}370371	fn get32(&self, index: u32) -> Result<Option<Val>> {372		self.0.get32(self.0.len32() - index - 1)373	}374375	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {376		self.0.get_lazy32(self.0.len32() - index - 1)377	}378379	fn is_cheap(&self) -> bool {380		self.0.is_cheap()381	}382}383384#[derive(Trace, Clone, Debug)]385pub enum ArrayMapper {386	Plain(NativeFn!((Val) -> Val)),387	WithIndex(NativeFn!((u32, Val) -> Val)),388}389390#[derive(Trace, Debug, Clone)]391pub struct MappedArray {392	inner: ArrValue,393	cached: Cc<RefCell<Vec<ArrayThunk>>>,394	mapper: ArrayMapper,395}396impl MappedArray {397	pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {398		let len = inner.len32();399		Self {400			inner,401			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),402			mapper,403		}404	}405	fn evaluate(&self, index: u32, value: Val) -> Result<Val> {406		match &self.mapper {407			ArrayMapper::Plain(f) => f.call(value),408			ArrayMapper::WithIndex(f) => f.call(index, value),409		}410	}411}412impl ArrayLike for MappedArray {413	fn len32(&self) -> u32 {414		arridx(self.cached.borrow().len())415	}416417	fn get32(&self, index: u32) -> Result<Option<Val>> {418		if index >= self.len32() {419			return Ok(None);420		}421		match &self.cached.borrow()[index as usize] {422			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),423			ArrayThunk::Errored(e) => return Err(e.clone()),424			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),425			ArrayThunk::Waiting => {}426		}427428		let ArrayThunk::Waiting = replace(429			&mut self.cached.borrow_mut()[index as usize],430			ArrayThunk::Pending,431		) else {432			unreachable!()433		};434435		let val = self436			.inner437			.get32(index)438			.transpose()439			.expect("index checked")440			.and_then(|r| self.evaluate(index, r));441442		let new_value = match val {443			Ok(v) => v,444			Err(e) => {445				self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());446				return Err(e);447			}448		};449		self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());450		Ok(Some(new_value))451	}452	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {453		#[derive(Trace)]454		struct MappedArrayThunk {455			arr: MappedArray,456			index: u32,457		}458		impl ThunkValue for MappedArrayThunk {459			type Output = Val;460461			fn get(&self) -> Result<Self::Output> {462				self.arr463					.get32(self.index)464					.transpose()465					.expect("index checked")466			}467		}468469		if index >= self.len32() {470			return None;471		}472		match &self.cached.borrow()[index as usize] {473			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),474			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),475			ArrayThunk::Waiting | ArrayThunk::Pending => {}476		}477478		Some(Thunk::new(MappedArrayThunk {479			arr: self.clone(),480			index,481		}))482	}483}484#[derive(Trace, Debug, Clone)]485pub struct MakeArray {486	cached: Cc<RefCell<Vec<ArrayThunk>>>,487	mapper: NativeFn!((u32,)->Val),488}489impl MakeArray {490	pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {491		Self {492			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),493			mapper,494		}495	}496}497impl ArrayLike for MakeArray {498	fn len32(&self) -> u32 {499		arridx(self.cached.borrow().len())500	}501502	fn get32(&self, index: u32) -> Result<Option<Val>> {503		if index >= self.len32() {504			return Ok(None);505		}506		match &self.cached.borrow()[index as usize] {507			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),508			ArrayThunk::Errored(e) => return Err(e.clone()),509			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),510			ArrayThunk::Waiting => {}511		}512513		let ArrayThunk::Waiting = replace(514			&mut self.cached.borrow_mut()[index as usize],515			ArrayThunk::Pending,516		) else {517			unreachable!()518		};519520		let val = self.mapper.call(index);521522		let new_value = match val {523			Ok(v) => v,524			Err(e) => {525				self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());526				return Err(e);527			}528		};529		self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());530		Ok(Some(new_value))531	}532	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {533		#[derive(Trace)]534		struct MakeArrayThunk {535			arr: MakeArray,536			index: u32,537		}538		impl ThunkValue for MakeArrayThunk {539			type Output = Val;540541			fn get(&self) -> Result<Self::Output> {542				self.arr543					.get32(self.index)544					.transpose()545					.expect("index checked")546			}547		}548549		if index >= self.len32() {550			return None;551		}552		match &self.cached.borrow()[index as usize] {553			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),554			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),555			ArrayThunk::Waiting | ArrayThunk::Pending => {}556		}557558		Some(Thunk::new(MakeArrayThunk {559			arr: self.clone(),560			index,561		}))562	}563}564565#[derive(Trace, Debug)]566pub struct RepeatedArray {567	data: ArrValue,568	repeats: u32,569	total_len: u32,570}571impl RepeatedArray {572	pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {573		let total_len = data.len32().checked_mul(repeats)?;574		Some(Self {575			data,576			repeats,577			total_len,578		})579	}580	fn map_idx(&self, index: u32) -> Option<u32> {581		if index > self.total_len {582			return None;583		}584		Some(index % self.data.len32())585	}586}587588impl ArrayLike for RepeatedArray {589	fn len32(&self) -> u32 {590		self.total_len591	}592593	fn get32(&self, index: u32) -> Result<Option<Val>> {594		let Some(idx) = self.map_idx(index) else {595			return Ok(None);596		};597		self.data.get32(idx)598	}599600	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {601		let idx = self.map_idx(index)?;602		self.data.get_lazy32(idx)603	}604605	fn is_cheap(&self) -> bool {606		self.data.is_cheap()607	}608}609610#[derive(Trace, Debug)]611pub struct PickObjectValues {612	obj: ObjValue,613	keys: Vec<IStr>,614}615616impl PickObjectValues {617	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {618		Self { obj, keys }619	}620}621622impl ArrayLike for PickObjectValues {623	fn len32(&self) -> u32 {624		arridx(self.keys.len())625	}626627	fn get32(&self, index: u32) -> Result<Option<Val>> {628		let Some(key) = self.keys.as_slice().get(index as usize) else {629			return Ok(None);630		};631		Ok(Some(self.obj.get_or_bail(key.clone())?))632	}633634	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {635		let key = self.keys.as_slice().get(index as usize)?;636		Some(self.obj.get_lazy_or_bail(key.clone()))637	}638639	fn is_cheap(&self) -> bool {640		false641	}642}643644#[derive(Trace, Debug)]645pub struct PickObjectKeyValues {646	obj: ObjValue,647	keys: Vec<IStr>,648}649650impl PickObjectKeyValues {651	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {652		Self { obj, keys }653	}654}655656#[derive(Typed, IntoUntyped)]657pub struct KeyValue {658	key: IStr,659	value: Thunk<Val>,660}661662impl ArrayLike for PickObjectKeyValues {663	fn len32(&self) -> u32 {664		arridx(self.keys.len())665	}666667	fn get32(&self, index: u32) -> Result<Option<Val>> {668		let Some(key) = self.keys.as_slice().get(index as usize) else {669			return Ok(None);670		};671		Ok(Some(672			KeyValue::into_untyped(KeyValue {673				key: key.clone(),674				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),675			})676			.expect("convertible"),677		))678	}679680	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {681		let key = self.keys.as_slice().get(index as usize)?;682		// Nothing can fail in the key part, yet value is still683		// lazy-evaluated684		Some(Thunk::evaluated(685			KeyValue::into_untyped(KeyValue {686				key: key.clone(),687				value: self.obj.get_lazy_or_bail(key.clone()),688			})689			.expect("convertible"),690		))691	}692693	fn is_cheap(&self) -> bool {694		false695	}696}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -52,15 +52,11 @@
 }
 
 pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {
-	// TODO: Eager trivial array
-	Some(match expr {
-		LExpr::Str(s) => Val::string(s.clone()),
-		LExpr::Num(n) => Val::Num(*n),
-		LExpr::Bool(false) => Val::Bool(false),
-		LExpr::Bool(true) => Val::Bool(true),
-		LExpr::Null => Val::Null,
-		_ => return None,
-	})
+	if let LExpr::Trivial(tv) = expr {
+		Some(tv.clone().into())
+	} else {
+		None
+	}
 }
 
 pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {
@@ -119,13 +115,24 @@
 pub fn evaluate(mut ctx: Context, mut expr: &LExpr) -> Result<Val> {
 	loop {
 		return Ok(match expr {
-			LExpr::Null => Val::Null,
-			LExpr::Bool(b) => Val::Bool(*b),
-			LExpr::Str(s) => Val::string(s.clone()),
-			LExpr::Num(n) => Val::Num(*n),
+			LExpr::Trivial(tv) => tv.clone().into(),
 			LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,
 			LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
-			LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),
+			LExpr::ArrConst(rc) => Val::Arr(ArrValue::new(rc.clone())),
+			LExpr::Arr { shape, items } => {
+				let inner = Context::enter_using(&ctx, shape);
+				'eager: {
+					let mut out: Vec<Val> = Vec::with_capacity(items.len());
+					for item in items.iter() {
+						let Ok(r) = evaluate(inner.clone(), item) else {
+							break 'eager;
+						};
+						out.push(r);
+					}
+					return Ok(Val::Arr(ArrValue::new(out)));
+				}
+				Val::Arr(ArrValue::expr(inner, items.clone()))
+			}
 			LExpr::UnaryOp(op, value) => {
 				let value = evaluate(ctx, value)?;
 				evaluate_unary_op(*op, &value)?
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -10,7 +10,7 @@
 
 use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};
 use jrsonnet_interner::IStr;
-use jrsonnet_ir::BinaryOpType;
+use jrsonnet_ir::{BinaryOpType, TrivialVal};
 pub use jrsonnet_macros::Thunk;
 use jrsonnet_types::ValType;
 use rustc_hash::FxHashMap;
@@ -621,6 +621,16 @@
 		Self::Bool(value)
 	}
 }
+impl From<TrivialVal> for Val {
+	fn from(tv: TrivialVal) -> Self {
+		match tv {
+			TrivialVal::Null => Self::Null,
+			TrivialVal::Bool(b) => Self::Bool(b),
+			TrivialVal::Num(n) => Self::Num(n),
+			TrivialVal::Str(s) => Self::string(s),
+		}
+	}
+}
 
 const fn is_function_like(val: &Val) -> bool {
 	matches!(val, Val::Func(_))