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
before · 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};1112use super::{ArrValue, arridx};13use crate::{14	Context, Error, ObjValue, Result, Thunk, Val,15	analyze::{ClosureShape, LExpr},16	error::ErrorKind::InfiniteRecursionDetected,17	evaluate::evaluate,18	function::NativeFn,19	typed::{IntoUntyped, Typed},20	val::ThunkValue,21};2223pub trait ArrayLike: Any + Trace + Debug {24	fn len32(&self) -> u32;25	fn is_empty(&self) -> bool {26		self.len32() == 027	}28	fn get32(&self, index: u32) -> Result<Option<Val>>;29	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>>;3031	fn is_cheap(&self) -> bool {32		false33	}34}35trait ArrayCheap {36	fn get(&self, index: u32) -> Option<Val>;37	fn len(&self) -> u32;38}39impl<T> ArrayLike for T40where41	T: Any + Trace + Debug + ArrayCheap,42{43	fn len32(&self) -> u32 {44		<T as ArrayCheap>::len(self)45	}4647	fn get32(&self, index: u32) -> Result<Option<Val>> {48		Ok(<T as ArrayCheap>::get(self, index))49	}5051	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {52		<T as ArrayCheap>::get(self, index).map(Thunk::evaluated)53	}5455	fn is_cheap(&self) -> bool {56		true57	}58}5960impl ArrayCheap for () {61	fn len(&self) -> u32 {62		063	}64	fn get(&self, _index: u32) -> Option<Val> {65		None66	}67}6869#[derive(Debug, Trace)]70pub struct SliceArray {71	pub(crate) inner: ArrValue,72	pub(crate) from: u32,73	pub(crate) to: u32,74	pub(crate) step: u32,75}7677impl SliceArray {78	fn map_idx(&self, index: u32) -> u32 {79		self.from + self.step * index80	}81}82impl ArrayLike for SliceArray {83	fn len32(&self) -> u32 {84		(self.to - self.from).div_ceil(self.step)85	}8687	fn get32(&self, index: u32) -> Result<Option<Val>> {88		self.inner.get32(self.map_idx(index))89	}9091	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {92		self.inner.get_lazy32(self.map_idx(index))93	}9495	fn is_cheap(&self) -> bool {96		self.inner.is_cheap()97	}98}99100impl ArrayCheap for IBytes {101	fn len(&self) -> u32 {102		arridx(self.as_slice().len())103	}104	fn get(&self, index: u32) -> Option<Val> {105		self.as_slice()106			.get(index as usize)107			.map(|v| Val::Num((*v).into()))108	}109}110111#[derive(Debug, Trace, Clone)]112enum ArrayThunk {113	Computed(Val),114	Errored(Error),115	Waiting,116	Pending,117}118119#[derive(Debug, Trace, Clone)]120pub struct ExprArray {121	ctx: Context,122	src: Rc<Vec<LExpr>>,123	cached: Cc<RefCell<Vec<ArrayThunk>>>,124}125impl ExprArray {126	pub fn new(outer: Context, shape: &ClosureShape, src: Rc<Vec<LExpr>>) -> Self {127		Self {128			ctx: Context::enter_using(&outer, shape),129			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),130			src,131		}132	}133}134impl ArrayLike for ExprArray {135	fn len32(&self) -> u32 {136		arridx(self.cached.borrow().len())137	}138	fn get32(&self, index: u32) -> Result<Option<Val>> {139		if index >= self.len32() {140			return Ok(None);141		}142		match &self.cached.borrow()[index as usize] {143			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),144			ArrayThunk::Errored(e) => return Err(e.clone()),145			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),146			ArrayThunk::Waiting => {}147		}148149		let ArrayThunk::Waiting = replace(150			&mut self.cached.borrow_mut()[index as usize],151			ArrayThunk::Pending,152		) else {153			unreachable!()154		};155156		let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;157		self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());158		Ok(Some(new_value))159	}160	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {161		#[derive(Trace)]162		struct ExprArrThunk {163			expr: ExprArray,164			index: u32,165		}166		impl ThunkValue for ExprArrThunk {167			type Output = Val;168169			fn get(&self) -> Result<Self::Output> {170				self.expr171					.get32(self.index)172					.transpose()173					.expect("index checked")174			}175		}176177		if index >= self.len32() {178			return None;179		}180		match &self.cached.borrow()[index as usize] {181			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),182			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),183			ArrayThunk::Waiting | ArrayThunk::Pending => {}184		}185186		Some(Thunk::new(ExprArrThunk {187			expr: self.clone(),188			index,189		}))190	}191	fn is_cheap(&self) -> bool {192		false193	}194}195196#[derive(Trace, Debug)]197pub struct ExtendedArray {198	pub a: ArrValue,199	pub b: ArrValue,200	split: u32,201	len: u32,202}203impl ExtendedArray {204	pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {205		let a_len = a.len32();206		let b_len = b.len32();207		let len = a_len.checked_add(b_len)?;208		Some(Self {209			a,210			b,211			split: a_len,212			len,213		})214	}215}216217struct WithExactSize<I>(I, usize);218impl<I, T> Iterator for WithExactSize<I>219where220	I: Iterator<Item = T>,221{222	type Item = T;223224	fn next(&mut self) -> Option<Self::Item> {225		self.0.next()226	}227	fn nth(&mut self, n: usize) -> Option<Self::Item> {228		self.0.nth(n)229	}230	fn size_hint(&self) -> (usize, Option<usize>) {231		(self.1, Some(self.1))232	}233}234impl<I> DoubleEndedIterator for WithExactSize<I>235where236	I: DoubleEndedIterator,237{238	fn next_back(&mut self) -> Option<Self::Item> {239		self.0.next_back()240	}241	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {242		self.0.nth_back(n)243	}244}245impl<I> ExactSizeIterator for WithExactSize<I>246where247	I: Iterator,248{249	fn len(&self) -> usize {250		self.1251	}252}253impl ArrayLike for ExtendedArray {254	fn get32(&self, index: u32) -> Result<Option<Val>> {255		if self.split > index {256			self.a.get32(index)257		} else {258			self.b.get32(index - self.split)259		}260	}261	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {262		if self.split > index {263			self.a.get_lazy32(index)264		} else {265			self.b.get_lazy32(index - self.split)266		}267	}268269	fn len32(&self) -> u32 {270		self.len271	}272273	fn is_cheap(&self) -> bool {274		self.a.is_cheap() && self.b.is_cheap()275	}276}277278impl<T> ArrayLike for Vec<T>279where280	T: IntoUntyped + Trace + fmt::Debug,281	for<'a> &'a T: IntoUntyped,282{283	fn len32(&self) -> u32 {284		self.as_slice().len().try_into().unwrap_or(u32::MAX)285	}286287	fn get32(&self, index: u32) -> Result<Option<Val>> {288		let Some(elem) = self.as_slice().get(index as usize) else {289			return Ok(None);290		};291		IntoUntyped::into_untyped(elem).map(Some)292	}293294	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {295		let elem = self.as_slice().get(index as usize)?;296		Some(IntoUntyped::into_lazy_untyped(elem))297	}298299	fn is_cheap(&self) -> bool {300		!T::provides_lazy()301	}302}303304/// Inclusive range type305#[derive(Debug, Trace, PartialEq, Eq)]306pub struct RangeArray {307	start: i32,308	end: i32,309}310impl RangeArray {311	pub fn empty() -> Self {312		Self::new_exclusive(0, 0)313	}314	pub fn new_exclusive(start: i32, end: i32) -> Self {315		end.checked_sub(1)316			.map_or_else(Self::empty, |end| Self { start, end })317	}318	pub fn new_inclusive(start: i32, end: i32) -> Self {319		Self { start, end }320	}321	#[expect(322		clippy::cast_sign_loss,323		reason = "the math is valid with wrapping, sign loss works as intended"324	)]325	fn size(&self) -> u32 {326		(self.end as u32)327			.wrapping_sub(self.start as u32)328			.wrapping_add(1)329	}330	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {331		WithExactSize(self.start..=self.end, self.size() as usize)332	}333}334impl ArrayCheap for RangeArray {335	fn get(&self, index: u32) -> Option<Val> {336		self.range().nth(index as usize).map(|i| Val::Num(i.into()))337	}338	fn len(&self) -> u32 {339		self.size()340	}341}342343#[derive(Debug, Trace)]344pub struct ReverseArray(pub ArrValue);345impl ArrayLike for ReverseArray {346	fn len32(&self) -> u32 {347		self.0.len32()348	}349350	fn get32(&self, index: u32) -> Result<Option<Val>> {351		self.0.get32(self.0.len32() - index - 1)352	}353354	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {355		self.0.get_lazy32(self.0.len32() - index - 1)356	}357358	fn is_cheap(&self) -> bool {359		self.0.is_cheap()360	}361}362363#[derive(Trace, Clone, Debug)]364pub enum ArrayMapper {365	Plain(NativeFn!((Val) -> Val)),366	WithIndex(NativeFn!((u32, Val) -> Val)),367}368369#[derive(Trace, Debug, Clone)]370pub struct MappedArray {371	inner: ArrValue,372	cached: Cc<RefCell<Vec<ArrayThunk>>>,373	mapper: ArrayMapper,374}375impl MappedArray {376	pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {377		let len = inner.len32();378		Self {379			inner,380			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),381			mapper,382		}383	}384	fn evaluate(&self, index: u32, value: Val) -> Result<Val> {385		match &self.mapper {386			ArrayMapper::Plain(f) => f.call(value),387			ArrayMapper::WithIndex(f) => f.call(index, value),388		}389	}390}391impl ArrayLike for MappedArray {392	fn len32(&self) -> u32 {393		arridx(self.cached.borrow().len())394	}395396	fn get32(&self, index: u32) -> Result<Option<Val>> {397		if index >= self.len32() {398			return Ok(None);399		}400		match &self.cached.borrow()[index as usize] {401			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),402			ArrayThunk::Errored(e) => return Err(e.clone()),403			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),404			ArrayThunk::Waiting => {}405		}406407		let ArrayThunk::Waiting = replace(408			&mut self.cached.borrow_mut()[index as usize],409			ArrayThunk::Pending,410		) else {411			unreachable!()412		};413414		let val = self415			.inner416			.get32(index)417			.transpose()418			.expect("index checked")419			.and_then(|r| self.evaluate(index, r));420421		let new_value = match val {422			Ok(v) => v,423			Err(e) => {424				self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());425				return Err(e);426			}427		};428		self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());429		Ok(Some(new_value))430	}431	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {432		#[derive(Trace)]433		struct MappedArrayThunk {434			arr: MappedArray,435			index: u32,436		}437		impl ThunkValue for MappedArrayThunk {438			type Output = Val;439440			fn get(&self) -> Result<Self::Output> {441				self.arr442					.get32(self.index)443					.transpose()444					.expect("index checked")445			}446		}447448		if index >= self.len32() {449			return None;450		}451		match &self.cached.borrow()[index as usize] {452			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),453			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),454			ArrayThunk::Waiting | ArrayThunk::Pending => {}455		}456457		Some(Thunk::new(MappedArrayThunk {458			arr: self.clone(),459			index,460		}))461	}462}463#[derive(Trace, Debug, Clone)]464pub struct MakeArray {465	cached: Cc<RefCell<Vec<ArrayThunk>>>,466	mapper: NativeFn!((u32,)->Val),467}468impl MakeArray {469	pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {470		Self {471			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),472			mapper,473		}474	}475}476impl ArrayLike for MakeArray {477	fn len32(&self) -> u32 {478		arridx(self.cached.borrow().len())479	}480481	fn get32(&self, index: u32) -> Result<Option<Val>> {482		if index >= self.len32() {483			return Ok(None);484		}485		match &self.cached.borrow()[index as usize] {486			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),487			ArrayThunk::Errored(e) => return Err(e.clone()),488			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),489			ArrayThunk::Waiting => {}490		}491492		let ArrayThunk::Waiting = replace(493			&mut self.cached.borrow_mut()[index as usize],494			ArrayThunk::Pending,495		) else {496			unreachable!()497		};498499		let val = self.mapper.call(index);500501		let new_value = match val {502			Ok(v) => v,503			Err(e) => {504				self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());505				return Err(e);506			}507		};508		self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());509		Ok(Some(new_value))510	}511	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {512		#[derive(Trace)]513		struct MakeArrayThunk {514			arr: MakeArray,515			index: u32,516		}517		impl ThunkValue for MakeArrayThunk {518			type Output = Val;519520			fn get(&self) -> Result<Self::Output> {521				self.arr522					.get32(self.index)523					.transpose()524					.expect("index checked")525			}526		}527528		if index >= self.len32() {529			return None;530		}531		match &self.cached.borrow()[index as usize] {532			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),533			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),534			ArrayThunk::Waiting | ArrayThunk::Pending => {}535		}536537		Some(Thunk::new(MakeArrayThunk {538			arr: self.clone(),539			index,540		}))541	}542}543544#[derive(Trace, Debug)]545pub struct RepeatedArray {546	data: ArrValue,547	repeats: u32,548	total_len: u32,549}550impl RepeatedArray {551	pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {552		let total_len = data.len32().checked_mul(repeats)?;553		Some(Self {554			data,555			repeats,556			total_len,557		})558	}559	fn map_idx(&self, index: u32) -> Option<u32> {560		if index > self.total_len {561			return None;562		}563		Some(index % self.data.len32())564	}565}566567impl ArrayLike for RepeatedArray {568	fn len32(&self) -> u32 {569		self.total_len570	}571572	fn get32(&self, index: u32) -> Result<Option<Val>> {573		let Some(idx) = self.map_idx(index) else {574			return Ok(None);575		};576		self.data.get32(idx)577	}578579	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {580		let idx = self.map_idx(index)?;581		self.data.get_lazy32(idx)582	}583584	fn is_cheap(&self) -> bool {585		self.data.is_cheap()586	}587}588589#[derive(Trace, Debug)]590pub struct PickObjectValues {591	obj: ObjValue,592	keys: Vec<IStr>,593}594595impl PickObjectValues {596	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {597		Self { obj, keys }598	}599}600601impl ArrayLike for PickObjectValues {602	fn len32(&self) -> u32 {603		arridx(self.keys.len())604	}605606	fn get32(&self, index: u32) -> Result<Option<Val>> {607		let Some(key) = self.keys.as_slice().get(index as usize) else {608			return Ok(None);609		};610		Ok(Some(self.obj.get_or_bail(key.clone())?))611	}612613	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {614		let key = self.keys.as_slice().get(index as usize)?;615		Some(self.obj.get_lazy_or_bail(key.clone()))616	}617618	fn is_cheap(&self) -> bool {619		false620	}621}622623#[derive(Trace, Debug)]624pub struct PickObjectKeyValues {625	obj: ObjValue,626	keys: Vec<IStr>,627}628629impl PickObjectKeyValues {630	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {631		Self { obj, keys }632	}633}634635#[derive(Typed, IntoUntyped)]636pub struct KeyValue {637	key: IStr,638	value: Thunk<Val>,639}640641impl ArrayLike for PickObjectKeyValues {642	fn len32(&self) -> u32 {643		arridx(self.keys.len())644	}645646	fn get32(&self, index: u32) -> Result<Option<Val>> {647		let Some(key) = self.keys.as_slice().get(index as usize) else {648			return Ok(None);649		};650		Ok(Some(651			KeyValue::into_untyped(KeyValue {652				key: key.clone(),653				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),654			})655			.expect("convertible"),656		))657	}658659	fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {660		let key = self.keys.as_slice().get(index as usize)?;661		// Nothing can fail in the key part, yet value is still662		// lazy-evaluated663		Some(Thunk::evaluated(664			KeyValue::into_untyped(KeyValue {665				key: key.clone(),666				value: self.obj.get_lazy_or_bail(key.clone()),667			})668			.expect("convertible"),669		))670	}671672	fn is_cheap(&self) -> bool {673		false674	}675}
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(_))