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}
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]