git.delta.rocks / jrsonnet / refs/commits / 4944c9eb9e42

difftreelog

refactor drop CharArray

rzmtutyqYaroslav Bolyukin2026-04-04parent: #6f6b79f.patch.diff
in: master

5 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -45,10 +45,6 @@
 		Some(Self::new(RepeatedArray::new(data, repeats)?))
 	}
 
-	pub fn chars(chars: impl Iterator<Item = char>) -> Self {
-		Self::new(CharArray(chars.collect()))
-	}
-
 	#[must_use]
 	pub fn map(self, mapper: NativeFn!((Val) -> Val)) -> Self {
 		Self::new(<MappedArray>::new(self, ArrayMapper::Plain(mapper)))
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};11use jrsonnet_ir::Expr;1213use super::ArrValue;14use crate::{15	Context, Error, ObjValue, Result, Thunk, Val,16	error::ErrorKind::InfiniteRecursionDetected,17	evaluate,18	function::NativeFn,19	typed::{IntoUntyped, Typed},20	val::ThunkValue,21};2223pub trait ArrayLike: Any + Trace + Debug {24	fn len(&self) -> usize;25	fn is_empty(&self) -> bool {26		self.len() == 027	}28	fn get(&self, index: usize) -> Result<Option<Val>>;29	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;3031	fn is_cheap(&self) -> bool {32		false33	}34}35trait ArrayCheap {36	fn get(&self, index: usize) -> Option<Val>;37	fn len(&self) -> usize;38}39impl<T> ArrayLike for T40where41	T: Any + Trace + Debug + ArrayCheap,42{43	fn len(&self) -> usize {44		<T as ArrayCheap>::len(self)45	}4647	fn get(&self, index: usize) -> Result<Option<Val>> {48		Ok(<T as ArrayCheap>::get(self, index))49	}5051	fn get_lazy(&self, index: usize) -> 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) -> usize {62		063	}64	fn get(&self, _index: usize) -> 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: usize) -> usize {79		self.from as usize + self.step as usize * index80	}81}82impl ArrayLike for SliceArray {83	fn len(&self) -> usize {84		(self.to - self.from).div_ceil(self.step) as usize85	}8687	fn get(&self, index: usize) -> Result<Option<Val>> {88		self.inner.get(self.map_idx(index))89	}9091	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {92		self.inner.get_lazy(self.map_idx(index))93	}9495	fn is_cheap(&self) -> bool {96		self.inner.is_cheap()97	}98}99100#[derive(Trace, Debug)]101pub struct CharArray(pub Vec<char>);102impl ArrayCheap for CharArray {103	fn len(&self) -> usize {104		self.0.as_slice().len()105	}106	fn get(&self, index: usize) -> Option<Val> {107		self.0.as_slice().get(index).map(|v| Val::string(*v))108	}109}110111impl ArrayCheap for IBytes {112	fn len(&self) -> usize {113		self.as_slice().len()114	}115	fn get(&self, index: usize) -> Option<Val> {116		self.as_slice().get(index).map(|v| Val::Num((*v).into()))117	}118}119120#[derive(Debug, Trace, Clone)]121enum ArrayThunk {122	Computed(Val),123	Errored(Error),124	Waiting,125	Pending,126}127128#[derive(Debug, Trace, Clone)]129pub struct ExprArray {130	ctx: Context,131	src: Rc<Vec<Expr>>,132	cached: Cc<RefCell<Vec<ArrayThunk>>>,133}134impl ExprArray {135	pub fn new(ctx: Context, src: Rc<Vec<Expr>>) -> Self {136		Self {137			ctx,138			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),139			src,140		}141	}142}143impl ArrayLike for ExprArray {144	fn len(&self) -> usize {145		self.cached.borrow().len()146	}147	fn get(&self, index: usize) -> Result<Option<Val>> {148		if index >= self.len() {149			return Ok(None);150		}151		match &self.cached.borrow()[index] {152			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),153			ArrayThunk::Errored(e) => return Err(e.clone()),154			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),155			ArrayThunk::Waiting => {}156		}157158		let ArrayThunk::Waiting =159			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)160		else {161			unreachable!()162		};163164		let new_value = match evaluate(self.ctx.clone(), &self.src[index]) {165			Ok(v) => v,166			Err(e) => {167				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());168				return Err(e);169			}170		};171		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());172		Ok(Some(new_value))173	}174	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {175		#[derive(Trace)]176		struct ExprArrThunk {177			expr: ExprArray,178			index: usize,179		}180		impl ThunkValue for ExprArrThunk {181			type Output = Val;182183			fn get(&self) -> Result<Self::Output> {184				self.expr185					.get(self.index)186					.transpose()187					.expect("index checked")188			}189		}190191		if index >= self.len() {192			return None;193		}194		match &self.cached.borrow()[index] {195			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),196			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),197			ArrayThunk::Waiting | ArrayThunk::Pending => {}198		}199200		Some(Thunk::new(ExprArrThunk {201			expr: self.clone(),202			index,203		}))204	}205	fn is_cheap(&self) -> bool {206		false207	}208}209210#[derive(Trace, Debug)]211pub struct ExtendedArray {212	pub a: ArrValue,213	pub b: ArrValue,214	split: usize,215	len: usize,216}217impl ExtendedArray {218	pub fn new(a: ArrValue, b: ArrValue) -> Self {219		let a_len = a.len();220		let b_len = b.len();221		Self {222			a,223			b,224			split: a_len,225			len: a_len.checked_add(b_len).expect("too large array value"),226		}227	}228}229230struct WithExactSize<I>(I, usize);231impl<I, T> Iterator for WithExactSize<I>232where233	I: Iterator<Item = T>,234{235	type Item = T;236237	fn next(&mut self) -> Option<Self::Item> {238		self.0.next()239	}240	fn nth(&mut self, n: usize) -> Option<Self::Item> {241		self.0.nth(n)242	}243	fn size_hint(&self) -> (usize, Option<usize>) {244		(self.1, Some(self.1))245	}246}247impl<I> DoubleEndedIterator for WithExactSize<I>248where249	I: DoubleEndedIterator,250{251	fn next_back(&mut self) -> Option<Self::Item> {252		self.0.next_back()253	}254	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {255		self.0.nth_back(n)256	}257}258impl<I> ExactSizeIterator for WithExactSize<I>259where260	I: Iterator,261{262	fn len(&self) -> usize {263		self.1264	}265}266impl ArrayLike for ExtendedArray {267	fn get(&self, index: usize) -> Result<Option<Val>> {268		if self.split > index {269			self.a.get(index)270		} else {271			self.b.get(index - self.split)272		}273	}274	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {275		if self.split > index {276			self.a.get_lazy(index)277		} else {278			self.b.get_lazy(index - self.split)279		}280	}281282	fn len(&self) -> usize {283		self.len284	}285286	fn is_cheap(&self) -> bool {287		self.a.is_cheap() && self.b.is_cheap()288	}289}290291impl<T> ArrayLike for Vec<T>292where293	T: IntoUntyped + Trace + fmt::Debug,294	for<'a> &'a T: IntoUntyped,295{296	fn len(&self) -> usize {297		self.as_slice().len()298	}299300	fn get(&self, index: usize) -> Result<Option<Val>> {301		let Some(elem) = self.as_slice().get(index) else {302			return Ok(None);303		};304		IntoUntyped::into_untyped(elem).map(Some)305	}306307	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {308		let elem = self.as_slice().get(index)?;309		Some(IntoUntyped::into_lazy_untyped(elem))310	}311312	fn is_cheap(&self) -> bool {313		!T::provides_lazy()314	}315}316317/// Inclusive range type318#[derive(Debug, Trace, PartialEq, Eq)]319pub struct RangeArray {320	start: i32,321	end: i32,322}323impl RangeArray {324	pub fn empty() -> Self {325		Self::new_exclusive(0, 0)326	}327	pub fn new_exclusive(start: i32, end: i32) -> Self {328		end.checked_sub(1)329			.map_or_else(Self::empty, |end| Self { start, end })330	}331	pub fn new_inclusive(start: i32, end: i32) -> Self {332		Self { start, end }333	}334	#[expect(335		clippy::cast_sign_loss,336		reason = "the math is valid with wrapping, sign loss works as intended"337	)]338	fn size(&self) -> usize {339		(self.end as usize)340			.wrapping_sub(self.start as usize)341			.wrapping_add(1)342	}343	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {344		WithExactSize(self.start..=self.end, self.size())345	}346}347impl ArrayCheap for RangeArray {348	fn get(&self, index: usize) -> Option<Val> {349		self.range().nth(index).map(|i| Val::Num(i.into()))350	}351	fn len(&self) -> usize {352		self.size()353	}354}355356#[derive(Debug, Trace)]357pub struct ReverseArray(pub ArrValue);358impl ArrayLike for ReverseArray {359	fn len(&self) -> usize {360		self.0.len()361	}362363	fn get(&self, index: usize) -> Result<Option<Val>> {364		self.0.get(self.0.len() - index - 1)365	}366367	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {368		self.0.get_lazy(self.0.len() - index - 1)369	}370371	fn is_cheap(&self) -> bool {372		self.0.is_cheap()373	}374}375376#[derive(Trace, Clone, Debug)]377pub enum ArrayMapper {378	Plain(NativeFn!((Val) -> Val)),379	WithIndex(NativeFn!((u32, Val) -> Val)),380}381382#[derive(Trace, Debug, Clone)]383pub struct MappedArray {384	inner: ArrValue,385	cached: Cc<RefCell<Vec<ArrayThunk>>>,386	mapper: ArrayMapper,387}388impl MappedArray {389	pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {390		let len = inner.len();391		Self {392			inner,393			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len])),394			mapper,395		}396	}397	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {398		match &self.mapper {399			ArrayMapper::Plain(f) => f.call(value),400			#[expect(401				clippy::cast_possible_truncation,402				reason = "array len is limited to u31"403			)]404			ArrayMapper::WithIndex(f) => f.call(index as u32, value),405		}406	}407}408impl ArrayLike for MappedArray {409	fn len(&self) -> usize {410		self.cached.borrow().len()411	}412413	fn get(&self, index: usize) -> Result<Option<Val>> {414		if index >= self.len() {415			return Ok(None);416		}417		match &self.cached.borrow()[index] {418			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),419			ArrayThunk::Errored(e) => return Err(e.clone()),420			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),421			ArrayThunk::Waiting => {}422		}423424		let ArrayThunk::Waiting =425			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)426		else {427			unreachable!()428		};429430		let val = self431			.inner432			.get(index)433			.transpose()434			.expect("index checked")435			.and_then(|r| self.evaluate(index, r));436437		let new_value = match val {438			Ok(v) => v,439			Err(e) => {440				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());441				return Err(e);442			}443		};444		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());445		Ok(Some(new_value))446	}447	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {448		#[derive(Trace)]449		struct MappedArrayThunk {450			arr: MappedArray,451			index: usize,452		}453		impl ThunkValue for MappedArrayThunk {454			type Output = Val;455456			fn get(&self) -> Result<Self::Output> {457				self.arr.get(self.index).transpose().expect("index checked")458			}459		}460461		if index >= self.len() {462			return None;463		}464		match &self.cached.borrow()[index] {465			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),466			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),467			ArrayThunk::Waiting | ArrayThunk::Pending => {}468		}469470		Some(Thunk::new(MappedArrayThunk {471			arr: self.clone(),472			index,473		}))474	}475}476477#[derive(Trace, Debug)]478pub struct RepeatedArray {479	data: ArrValue,480	repeats: usize,481	total_len: usize,482}483impl RepeatedArray {484	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {485		let total_len = data.len().checked_mul(repeats)?;486		Some(Self {487			data,488			repeats,489			total_len,490		})491	}492	fn map_idx(&self, index: usize) -> Option<usize> {493		if index > self.total_len {494			return None;495		}496		Some(index % self.data.len())497	}498}499500impl ArrayLike for RepeatedArray {501	fn len(&self) -> usize {502		self.total_len503	}504505	fn get(&self, index: usize) -> Result<Option<Val>> {506		let Some(idx) = self.map_idx(index) else {507			return Ok(None);508		};509		self.data.get(idx)510	}511512	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {513		let idx = self.map_idx(index)?;514		self.data.get_lazy(idx)515	}516517	fn is_cheap(&self) -> bool {518		self.data.is_cheap()519	}520}521522#[derive(Trace, Debug)]523pub struct PickObjectValues {524	obj: ObjValue,525	keys: Vec<IStr>,526}527528impl PickObjectValues {529	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {530		Self { obj, keys }531	}532}533534impl ArrayLike for PickObjectValues {535	fn len(&self) -> usize {536		self.keys.len()537	}538539	fn get(&self, index: usize) -> Result<Option<Val>> {540		let Some(key) = self.keys.as_slice().get(index) else {541			return Ok(None);542		};543		Ok(Some(self.obj.get_or_bail(key.clone())?))544	}545546	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {547		let key = self.keys.as_slice().get(index)?;548		Some(self.obj.get_lazy_or_bail(key.clone()))549	}550551	fn is_cheap(&self) -> bool {552		false553	}554}555556#[derive(Trace, Debug)]557pub struct PickObjectKeyValues {558	obj: ObjValue,559	keys: Vec<IStr>,560}561562impl PickObjectKeyValues {563	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {564		Self { obj, keys }565	}566}567568#[derive(Typed, IntoUntyped)]569pub struct KeyValue {570	key: IStr,571	value: Thunk<Val>,572}573574impl ArrayLike for PickObjectKeyValues {575	fn len(&self) -> usize {576		self.keys.len()577	}578579	fn get(&self, index: usize) -> Result<Option<Val>> {580		let Some(key) = self.keys.as_slice().get(index) else {581			return Ok(None);582		};583		Ok(Some(584			KeyValue::into_untyped(KeyValue {585				key: key.clone(),586				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),587			})588			.expect("convertible"),589		))590	}591592	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {593		let key = self.keys.as_slice().get(index)?;594		// Nothing can fail in the key part, yet value is still595		// lazy-evaluated596		Some(Thunk::evaluated(597			KeyValue::into_untyped(KeyValue {598				key: key.clone(),599				value: self.obj.get_lazy_or_bail(key.clone()),600			})601			.expect("convertible"),602		))603	}604605	fn is_cheap(&self) -> bool {606		false607	}608}
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::Expr;1213use super::ArrValue;14use crate::{15	Context, Error, ObjValue, Result, Thunk, Val,16	error::ErrorKind::InfiniteRecursionDetected,17	evaluate,18	function::NativeFn,19	typed::{IntoUntyped, Typed},20	val::ThunkValue,21};2223pub trait ArrayLike: Any + Trace + Debug {24	fn len(&self) -> usize;25	fn is_empty(&self) -> bool {26		self.len() == 027	}28	fn get(&self, index: usize) -> Result<Option<Val>>;29	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;3031	fn is_cheap(&self) -> bool {32		false33	}34}35trait ArrayCheap {36	fn get(&self, index: usize) -> Option<Val>;37	fn len(&self) -> usize;38}39impl<T> ArrayLike for T40where41	T: Any + Trace + Debug + ArrayCheap,42{43	fn len(&self) -> usize {44		<T as ArrayCheap>::len(self)45	}4647	fn get(&self, index: usize) -> Result<Option<Val>> {48		Ok(<T as ArrayCheap>::get(self, index))49	}5051	fn get_lazy(&self, index: usize) -> 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) -> usize {62		063	}64	fn get(&self, _index: usize) -> 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: usize) -> usize {79		self.from as usize + self.step as usize * index80	}81}82impl ArrayLike for SliceArray {83	fn len(&self) -> usize {84		(self.to - self.from).div_ceil(self.step) as usize85	}8687	fn get(&self, index: usize) -> Result<Option<Val>> {88		self.inner.get(self.map_idx(index))89	}9091	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {92		self.inner.get_lazy(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) -> usize {102		self.as_slice().len()103	}104	fn get(&self, index: usize) -> Option<Val> {105		self.as_slice().get(index).map(|v| Val::Num((*v).into()))106	}107}108109#[derive(Debug, Trace, Clone)]110enum ArrayThunk {111	Computed(Val),112	Errored(Error),113	Waiting,114	Pending,115}116117#[derive(Debug, Trace, Clone)]118pub struct ExprArray {119	ctx: Context,120	src: Rc<Vec<Expr>>,121	cached: Cc<RefCell<Vec<ArrayThunk>>>,122}123impl ExprArray {124	pub fn new(ctx: Context, src: Rc<Vec<Expr>>) -> Self {125		Self {126			ctx,127			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),128			src,129		}130	}131}132impl ArrayLike for ExprArray {133	fn len(&self) -> usize {134		self.cached.borrow().len()135	}136	fn get(&self, index: usize) -> Result<Option<Val>> {137		if index >= self.len() {138			return Ok(None);139		}140		match &self.cached.borrow()[index] {141			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),142			ArrayThunk::Errored(e) => return Err(e.clone()),143			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),144			ArrayThunk::Waiting => {}145		}146147		let ArrayThunk::Waiting =148			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)149		else {150			unreachable!()151		};152153		let new_value = match evaluate(self.ctx.clone(), &self.src[index]) {154			Ok(v) => v,155			Err(e) => {156				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());157				return Err(e);158			}159		};160		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());161		Ok(Some(new_value))162	}163	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {164		#[derive(Trace)]165		struct ExprArrThunk {166			expr: ExprArray,167			index: usize,168		}169		impl ThunkValue for ExprArrThunk {170			type Output = Val;171172			fn get(&self) -> Result<Self::Output> {173				self.expr174					.get(self.index)175					.transpose()176					.expect("index checked")177			}178		}179180		if index >= self.len() {181			return None;182		}183		match &self.cached.borrow()[index] {184			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),185			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),186			ArrayThunk::Waiting | ArrayThunk::Pending => {}187		}188189		Some(Thunk::new(ExprArrThunk {190			expr: self.clone(),191			index,192		}))193	}194	fn is_cheap(&self) -> bool {195		false196	}197}198199#[derive(Trace, Debug)]200pub struct ExtendedArray {201	pub a: ArrValue,202	pub b: ArrValue,203	split: usize,204	len: usize,205}206impl ExtendedArray {207	pub fn new(a: ArrValue, b: ArrValue) -> Self {208		let a_len = a.len();209		let b_len = b.len();210		Self {211			a,212			b,213			split: a_len,214			len: a_len.checked_add(b_len).expect("too large array value"),215		}216	}217}218219struct WithExactSize<I>(I, usize);220impl<I, T> Iterator for WithExactSize<I>221where222	I: Iterator<Item = T>,223{224	type Item = T;225226	fn next(&mut self) -> Option<Self::Item> {227		self.0.next()228	}229	fn nth(&mut self, n: usize) -> Option<Self::Item> {230		self.0.nth(n)231	}232	fn size_hint(&self) -> (usize, Option<usize>) {233		(self.1, Some(self.1))234	}235}236impl<I> DoubleEndedIterator for WithExactSize<I>237where238	I: DoubleEndedIterator,239{240	fn next_back(&mut self) -> Option<Self::Item> {241		self.0.next_back()242	}243	fn nth_back(&mut self, n: usize) -> Option<Self::Item> {244		self.0.nth_back(n)245	}246}247impl<I> ExactSizeIterator for WithExactSize<I>248where249	I: Iterator,250{251	fn len(&self) -> usize {252		self.1253	}254}255impl ArrayLike for ExtendedArray {256	fn get(&self, index: usize) -> Result<Option<Val>> {257		if self.split > index {258			self.a.get(index)259		} else {260			self.b.get(index - self.split)261		}262	}263	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {264		if self.split > index {265			self.a.get_lazy(index)266		} else {267			self.b.get_lazy(index - self.split)268		}269	}270271	fn len(&self) -> usize {272		self.len273	}274275	fn is_cheap(&self) -> bool {276		self.a.is_cheap() && self.b.is_cheap()277	}278}279280impl<T> ArrayLike for Vec<T>281where282	T: IntoUntyped + Trace + fmt::Debug,283	for<'a> &'a T: IntoUntyped,284{285	fn len(&self) -> usize {286		self.as_slice().len()287	}288289	fn get(&self, index: usize) -> Result<Option<Val>> {290		let Some(elem) = self.as_slice().get(index) else {291			return Ok(None);292		};293		IntoUntyped::into_untyped(elem).map(Some)294	}295296	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {297		let elem = self.as_slice().get(index)?;298		Some(IntoUntyped::into_lazy_untyped(elem))299	}300301	fn is_cheap(&self) -> bool {302		!T::provides_lazy()303	}304}305306/// Inclusive range type307#[derive(Debug, Trace, PartialEq, Eq)]308pub struct RangeArray {309	start: i32,310	end: i32,311}312impl RangeArray {313	pub fn empty() -> Self {314		Self::new_exclusive(0, 0)315	}316	pub fn new_exclusive(start: i32, end: i32) -> Self {317		end.checked_sub(1)318			.map_or_else(Self::empty, |end| Self { start, end })319	}320	pub fn new_inclusive(start: i32, end: i32) -> Self {321		Self { start, end }322	}323	#[expect(324		clippy::cast_sign_loss,325		reason = "the math is valid with wrapping, sign loss works as intended"326	)]327	fn size(&self) -> usize {328		(self.end as usize)329			.wrapping_sub(self.start as usize)330			.wrapping_add(1)331	}332	fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {333		WithExactSize(self.start..=self.end, self.size())334	}335}336impl ArrayCheap for RangeArray {337	fn get(&self, index: usize) -> Option<Val> {338		self.range().nth(index).map(|i| Val::Num(i.into()))339	}340	fn len(&self) -> usize {341		self.size()342	}343}344345#[derive(Debug, Trace)]346pub struct ReverseArray(pub ArrValue);347impl ArrayLike for ReverseArray {348	fn len(&self) -> usize {349		self.0.len()350	}351352	fn get(&self, index: usize) -> Result<Option<Val>> {353		self.0.get(self.0.len() - index - 1)354	}355356	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {357		self.0.get_lazy(self.0.len() - index - 1)358	}359360	fn is_cheap(&self) -> bool {361		self.0.is_cheap()362	}363}364365#[derive(Trace, Clone, Debug)]366pub enum ArrayMapper {367	Plain(NativeFn!((Val) -> Val)),368	WithIndex(NativeFn!((u32, Val) -> Val)),369}370371#[derive(Trace, Debug, Clone)]372pub struct MappedArray {373	inner: ArrValue,374	cached: Cc<RefCell<Vec<ArrayThunk>>>,375	mapper: ArrayMapper,376}377impl MappedArray {378	pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self {379		let len = inner.len();380		Self {381			inner,382			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len])),383			mapper,384		}385	}386	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {387		match &self.mapper {388			ArrayMapper::Plain(f) => f.call(value),389			#[expect(390				clippy::cast_possible_truncation,391				reason = "array len is limited to u31"392			)]393			ArrayMapper::WithIndex(f) => f.call(index as u32, value),394		}395	}396}397impl ArrayLike for MappedArray {398	fn len(&self) -> usize {399		self.cached.borrow().len()400	}401402	fn get(&self, index: usize) -> Result<Option<Val>> {403		if index >= self.len() {404			return Ok(None);405		}406		match &self.cached.borrow()[index] {407			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),408			ArrayThunk::Errored(e) => return Err(e.clone()),409			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),410			ArrayThunk::Waiting => {}411		}412413		let ArrayThunk::Waiting =414			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)415		else {416			unreachable!()417		};418419		let val = self420			.inner421			.get(index)422			.transpose()423			.expect("index checked")424			.and_then(|r| self.evaluate(index, r));425426		let new_value = match val {427			Ok(v) => v,428			Err(e) => {429				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());430				return Err(e);431			}432		};433		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());434		Ok(Some(new_value))435	}436	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {437		#[derive(Trace)]438		struct MappedArrayThunk {439			arr: MappedArray,440			index: usize,441		}442		impl ThunkValue for MappedArrayThunk {443			type Output = Val;444445			fn get(&self) -> Result<Self::Output> {446				self.arr.get(self.index).transpose().expect("index checked")447			}448		}449450		if index >= self.len() {451			return None;452		}453		match &self.cached.borrow()[index] {454			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),455			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),456			ArrayThunk::Waiting | ArrayThunk::Pending => {}457		}458459		Some(Thunk::new(MappedArrayThunk {460			arr: self.clone(),461			index,462		}))463	}464}465466#[derive(Trace, Debug)]467pub struct RepeatedArray {468	data: ArrValue,469	repeats: usize,470	total_len: usize,471}472impl RepeatedArray {473	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {474		let total_len = data.len().checked_mul(repeats)?;475		Some(Self {476			data,477			repeats,478			total_len,479		})480	}481	fn map_idx(&self, index: usize) -> Option<usize> {482		if index > self.total_len {483			return None;484		}485		Some(index % self.data.len())486	}487}488489impl ArrayLike for RepeatedArray {490	fn len(&self) -> usize {491		self.total_len492	}493494	fn get(&self, index: usize) -> Result<Option<Val>> {495		let Some(idx) = self.map_idx(index) else {496			return Ok(None);497		};498		self.data.get(idx)499	}500501	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {502		let idx = self.map_idx(index)?;503		self.data.get_lazy(idx)504	}505506	fn is_cheap(&self) -> bool {507		self.data.is_cheap()508	}509}510511#[derive(Trace, Debug)]512pub struct PickObjectValues {513	obj: ObjValue,514	keys: Vec<IStr>,515}516517impl PickObjectValues {518	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {519		Self { obj, keys }520	}521}522523impl ArrayLike for PickObjectValues {524	fn len(&self) -> usize {525		self.keys.len()526	}527528	fn get(&self, index: usize) -> Result<Option<Val>> {529		let Some(key) = self.keys.as_slice().get(index) else {530			return Ok(None);531		};532		Ok(Some(self.obj.get_or_bail(key.clone())?))533	}534535	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {536		let key = self.keys.as_slice().get(index)?;537		Some(self.obj.get_lazy_or_bail(key.clone()))538	}539540	fn is_cheap(&self) -> bool {541		false542	}543}544545#[derive(Trace, Debug)]546pub struct PickObjectKeyValues {547	obj: ObjValue,548	keys: Vec<IStr>,549}550551impl PickObjectKeyValues {552	pub fn new(obj: ObjValue, keys: Vec<IStr>) -> Self {553		Self { obj, keys }554	}555}556557#[derive(Typed, IntoUntyped)]558pub struct KeyValue {559	key: IStr,560	value: Thunk<Val>,561}562563impl ArrayLike for PickObjectKeyValues {564	fn len(&self) -> usize {565		self.keys.len()566	}567568	fn get(&self, index: usize) -> Result<Option<Val>> {569		let Some(key) = self.keys.as_slice().get(index) else {570			return Ok(None);571		};572		Ok(Some(573			KeyValue::into_untyped(KeyValue {574				key: key.clone(),575				value: Thunk::evaluated(self.obj.get_or_bail(key.clone())?),576			})577			.expect("convertible"),578		))579	}580581	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {582		let key = self.keys.as_slice().get(index)?;583		// Nothing can fail in the key part, yet value is still584		// lazy-evaluated585		Some(Thunk::evaluated(586			KeyValue::into_untyped(KeyValue {587				key: key.clone(),588				value: self.obj.get_lazy_or_bail(key.clone()),589			})590			.expect("convertible"),591		))592	}593594	fn is_cheap(&self) -> bool {595		false596	}597}
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -463,6 +463,11 @@
 impl Typed for char {
 	const TYPE: &'static ComplexValType = &ComplexValType::Char;
 }
+impl IntoUntyped for &char {
+	fn into_untyped(value: Self) -> Result<Val> {
+		Ok(Val::string(*value))
+	}
+}
 impl IntoUntyped for char {
 	fn into_untyped(value: Self) -> Result<Val> {
 		Ok(Val::string(value))
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -266,7 +266,7 @@
 
 	pub fn to_array(self) -> ArrValue {
 		match self {
-			Self::Str(s) => ArrValue::chars(s.chars()),
+			Self::Str(s) => s.chars().collect(),
 			Self::Arr(arr) => arr,
 		}
 	}
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -223,7 +223,7 @@
 
 #[builtin]
 pub fn builtin_string_chars(str: IStr) -> ArrValue {
-	ArrValue::chars(str.chars())
+	str.chars().collect()
 }
 
 #[builtin]