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}
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(_))