git.delta.rocks / jrsonnet / refs/commits / 23d571a0df03

difftreelog

refactor move arrays to use dyn ArrayLike

Yaroslav Bolyukin2023-08-06parent: #09dae32.patch.diff
in: master

8 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
@@ -1,44 +1,20 @@
-use std::rc::Rc;
+use std::any::Any;
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IBytes;
 use jrsonnet_parser::LocExpr;
 
-use crate::{function::FuncVal, Context, Result, Thunk, Val};
+use crate::{function::FuncVal, gc::TraceBox, tb, Context, Result, Thunk, Val};
 
 mod spec;
-use spec::*;
+pub use spec::ArrayLike;
+pub(crate) use spec::*;
 
 /// Represents a Jsonnet array value.
 #[derive(Debug, Clone, Trace)]
 // may contrain other ArrValue
 #[trace(tracking(force))]
-pub enum ArrValue {
-	/// Layout optimized byte array.
-	Bytes(BytesArray),
-	/// Layout optimized char array.
-	Chars(CharArray),
-	/// Every element is lazy evaluated.
-	Lazy(LazyArray),
-	/// Every element is defined somewhere in source code
-	Expr(ExprArray),
-	/// Every field is already evaluated.
-	Eager(EagerArray),
-	/// Concatenation of two arrays of any kind.
-	Extended(Cc<ExtendedArray>),
-	/// Represents a integer array in form `[start, start + 1, ... end - 1, end]`.
-	/// This kind of arrays is generated by `std.range(start, end)` call, and used for loops.
-	Range(RangeArray),
-	/// Sliced array view.
-	Slice(Cc<SliceArray>),
-	/// Reversed array view.
-	/// Returned by `std.reverse(other)` call
-	Reverse(Cc<ReverseArray>),
-	/// Returned by `std.map` call
-	Mapped(MappedArray),
-	/// Returned by `std.repeat` call
-	Repeated(RepeatedArray),
-}
+pub struct ArrValue(Cc<TraceBox<dyn ArrayLike>>);
 
 pub trait ArrayLikeIter<T>: Iterator<Item = T> + DoubleEndedIterator + ExactSizeIterator {}
 impl<I, T> ArrayLikeIter<T> for I where
@@ -47,36 +23,39 @@
 }
 
 impl ArrValue {
+	pub fn new(v: impl ArrayLike) -> Self {
+		Self(Cc::new(tb!(v)))
+	}
 	pub fn empty() -> Self {
-		Self::Range(RangeArray::empty())
+		Self::new(RangeArray::empty())
 	}
 
 	pub fn expr(ctx: Context, exprs: impl IntoIterator<Item = LocExpr>) -> Self {
-		Self::Expr(ExprArray::new(ctx, exprs))
+		Self::new(ExprArray::new(ctx, exprs))
 	}
 
-	pub fn lazy(thunks: Cc<Vec<Thunk<Val>>>) -> Self {
-		Self::Lazy(LazyArray(thunks))
+	pub fn lazy(thunks: Vec<Thunk<Val>>) -> Self {
+		Self::new(LazyArray(thunks))
 	}
 
 	pub fn eager(values: Vec<Val>) -> Self {
-		Self::Eager(EagerArray(Cc::new(values)))
+		Self::new(EagerArray(values))
 	}
 
 	pub fn repeated(data: ArrValue, repeats: usize) -> Option<Self> {
-		Some(Self::Repeated(RepeatedArray::new(data, repeats)?))
+		Some(Self::new(RepeatedArray::new(data, repeats)?))
 	}
 
 	pub fn bytes(bytes: IBytes) -> Self {
-		Self::Bytes(BytesArray(bytes))
+		Self::new(BytesArray(bytes))
 	}
 	pub fn chars(chars: impl Iterator<Item = char>) -> Self {
-		Self::Chars(CharArray(Rc::new(chars.collect())))
+		Self::new(CharArray(chars.collect()))
 	}
 
 	#[must_use]
 	pub fn map(self, mapper: FuncVal) -> Self {
-		Self::Mapped(MappedArray::new(self, mapper))
+		Self::new(MappedArray::new(self, mapper))
 	}
 
 	pub fn filter(self, filter: impl Fn(&Val) -> Result<bool>) -> Result<Self> {
@@ -100,7 +79,7 @@
 		} else if b.is_empty() {
 			a
 		} else if a.len() + b.len() > ARR_EXTEND_THRESHOLD {
-			Self::Extended(Cc::new(ExtendedArray::new(a, b)))
+			Self::new(ExtendedArray::new(a, b))
 		} else if let (Some(a), Some(b)) = (a.iter_cheap(), b.iter_cheap()) {
 			let mut out = Vec::with_capacity(a.len() + b.len());
 			out.extend(a);
@@ -110,15 +89,15 @@
 			let mut out = Vec::with_capacity(a.len() + b.len());
 			out.extend(a.iter_lazy());
 			out.extend(b.iter_lazy());
-			Self::lazy(Cc::new(out))
+			Self::lazy(out)
 		}
 	}
 
 	pub fn range_exclusive(a: i32, b: i32) -> Self {
-		Self::Range(RangeArray::new_exclusive(a, b))
+		Self::new(RangeArray::new_exclusive(a, b))
 	}
 	pub fn range_inclusive(a: i32, b: i32) -> Self {
-		Self::Range(RangeArray::new_inclusive(a, b))
+		Self::new(RangeArray::new_inclusive(a, b))
 	}
 
 	#[must_use]
@@ -136,53 +115,42 @@
 		if from >= to || step == 0 {
 			return None;
 		}
-		// match self {
-		// 	ArrValue::Slice(slice) => {
-		// 		return Some(Self::Slice(Cc::new(SliceArray {
-		// 			inner: slice.inner.clone(),
-		// 			from: slice.from + slice.step * (from as u32),
-		// 			to: slice.from + (to as u32) * slice.step,
-		// 			step: slice.step * step as u32,
-		// 		})))
-		// 	}
-		// 	_ => {}
-		// }
 
-		Some(Self::Slice(Cc::new(SliceArray {
+		Some(Self::new(SliceArray {
 			inner: self,
 			from: from as u32,
 			to: to as u32,
 			step: step as u32,
-		})))
+		}))
 	}
 
 	/// Array length.
 	pub fn len(&self) -> usize {
-		pass!(self.len())
+		self.0.len()
 	}
 
 	/// Is array contains no elements?
 	pub fn is_empty(&self) -> bool {
-		pass!(self.is_empty())
+		self.0.is_empty()
 	}
 
 	/// Get array element by index, evaluating it, if it is lazy.
 	///
 	/// Returns `None` on out-of-bounds condition.
 	pub fn get(&self, index: usize) -> Result<Option<Val>> {
-		pass!(self.get(index))
+		self.0.get(index)
 	}
 
 	/// Returns None if get is either non cheap, or out of bounds
 	fn get_cheap(&self, index: usize) -> Option<Val> {
-		pass!(self.get_cheap(index))
+		self.0.get_cheap(index)
 	}
 
 	/// Get array element by index, without evaluation.
 	///
 	/// Returns `None` on out-of-bounds condition.
 	pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		pass!(self.get_lazy(index))
+		self.0.get_lazy(index)
 	}
 
 	pub fn iter(&self) -> impl ArrayLikeIter<Result<Val>> + '_ {
@@ -205,33 +173,20 @@
 	/// Return a reversed view on current array.
 	#[must_use]
 	pub fn reversed(self) -> Self {
-		Self::Reverse(Cc::new(ReverseArray(self)))
+		Self::new(ReverseArray(self))
 	}
 
 	pub fn ptr_eq(a: &Self, b: &Self) -> bool {
-		match (a, b) {
-			(ArrValue::Bytes(a), ArrValue::Bytes(b)) => a.0 == b.0,
-			(ArrValue::Lazy(a), ArrValue::Lazy(b)) => Cc::ptr_eq(&a.0, &b.0),
-			(ArrValue::Expr(a), ArrValue::Expr(b)) => Cc::ptr_eq(&a.0, &b.0),
-			(ArrValue::Eager(a), ArrValue::Eager(b)) => Cc::ptr_eq(&a.0, &b.0),
-			(ArrValue::Extended(a), ArrValue::Extended(b)) => Cc::ptr_eq(a, b),
-			(ArrValue::Range(a), ArrValue::Range(b)) => a == b,
-			_ => false,
-		}
+		Cc::ptr_eq(&a.0, &b.0)
 	}
 
 	/// Is this vec supports `.get_cheap()?`
 	pub fn is_cheap(&self) -> bool {
-		match self {
-			ArrValue::Eager(_) | ArrValue::Range(..) | ArrValue::Bytes(_) | ArrValue::Chars(_) => {
-				true
-			}
-			ArrValue::Extended(v) => v.a.is_cheap() && v.b.is_cheap(),
-			ArrValue::Slice(r) => r.inner.is_cheap(),
-			ArrValue::Reverse(i) => i.0.is_cheap(),
-			ArrValue::Repeated(v) => v.is_cheap(),
-			ArrValue::Expr(_) | ArrValue::Lazy(_) | ArrValue::Mapped(_) => false,
-		}
+		self.0.is_cheap()
+	}
+
+	pub fn as_any(&self) -> &dyn Any {
+		&self.0
 	}
 }
 impl From<Vec<Val>> for ArrValue {
@@ -241,7 +196,7 @@
 }
 impl From<Vec<Thunk<Val>>> for ArrValue {
 	fn from(value: Vec<Thunk<Val>>) -> Self {
-		Self::lazy(Cc::new(value))
+		Self::lazy(value)
 	}
 }
 impl FromIterator<Val> for ArrValue {
@@ -249,6 +204,27 @@
 		Self::eager(iter.into_iter().collect())
 	}
 }
+impl ArrayLike for ArrValue {
+	fn len(&self) -> usize {
+		self.0.len()
+	}
 
+	fn get(&self, index: usize) -> Result<Option<Val>> {
+		self.0.get(index)
+	}
+
+	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+		self.0.get_lazy(index)
+	}
+
+	fn get_cheap(&self, index: usize) -> Option<Val> {
+		self.0.get_cheap(index)
+	}
+
+	fn is_cheap(&self) -> bool {
+		self.0.is_cheap()
+	}
+}
+
 #[cfg(target_pointer_width = "64")]
-static_assertions::assert_eq_size!(ArrValue, [u8; 16]);
+static_assertions::assert_eq_size!(ArrValue, [u8; 8]);
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -1,4 +1,4 @@
-use std::{cell::RefCell, iter, mem::replace, rc::Rc};
+use std::{any::Any, cell::RefCell, fmt::Debug, iter, mem::replace};
 
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::{IBytes, IStr};
@@ -13,7 +13,7 @@
 	Context, Error, Result, Thunk, Val,
 };
 
-pub trait ArrayLike: Sized + Into<ArrValue> {
+pub trait ArrayLike: Any + Trace + Debug {
 	fn len(&self) -> usize;
 	fn is_empty(&self) -> bool {
 		self.len() == 0
@@ -22,12 +22,10 @@
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;
 	fn get_cheap(&self, index: usize) -> Option<Val>;
 
-	fn reverse(self) -> ArrValue {
-		ArrValue::Reverse(Cc::new(ReverseArray(self.into())))
-	}
+	fn is_cheap(&self) -> bool;
 }
 
-#[derive(Debug, Clone, Trace)]
+#[derive(Debug, Trace)]
 pub struct SliceArray {
 	pub(crate) inner: ArrValue,
 	pub(crate) from: u32,
@@ -81,15 +79,13 @@
 	fn get_cheap(&self, index: usize) -> Option<Val> {
 		self.iter_cheap()?.nth(index)
 	}
-}
-impl From<SliceArray> for ArrValue {
-	fn from(value: SliceArray) -> Self {
-		Self::Slice(Cc::new(value))
+	fn is_cheap(&self) -> bool {
+		self.inner.is_cheap()
 	}
 }
 
-#[derive(Trace, Debug, Clone)]
-pub struct CharArray(pub Rc<Vec<char>>);
+#[derive(Trace, Debug)]
+pub struct CharArray(pub Vec<char>);
 impl ArrayLike for CharArray {
 	fn len(&self) -> usize {
 		self.0.len()
@@ -108,14 +104,12 @@
 			.get(index)
 			.map(|v| Val::Str(StrValue::Flat(IStr::from(*v))))
 	}
-}
-impl From<CharArray> for ArrValue {
-	fn from(value: CharArray) -> Self {
-		ArrValue::Chars(value)
+	fn is_cheap(&self) -> bool {
+		true
 	}
 }
 
-#[derive(Trace, Debug, Clone)]
+#[derive(Trace, Debug)]
 pub struct BytesArray(pub IBytes);
 impl ArrayLike for BytesArray {
 	fn len(&self) -> usize {
@@ -133,10 +127,8 @@
 	fn get_cheap(&self, index: usize) -> Option<Val> {
 		self.0.get(index).map(|v| Val::Num(f64::from(*v)))
 	}
-}
-impl From<BytesArray> for ArrValue {
-	fn from(value: BytesArray) -> Self {
-		ArrValue::Bytes(value)
+	fn is_cheap(&self) -> bool {
+		true
 	}
 }
 
@@ -148,30 +140,30 @@
 	Pending,
 }
 
-#[derive(Debug, Trace)]
-pub struct ExprArrayInner {
+#[derive(Debug, Trace, Clone)]
+pub struct ExprArray {
 	ctx: Context,
-	cached: RefCell<Vec<ArrayThunk<LocExpr>>>,
+	cached: Cc<RefCell<Vec<ArrayThunk<LocExpr>>>>,
 }
-#[derive(Debug, Trace, Clone)]
-pub struct ExprArray(pub Cc<ExprArrayInner>);
 impl ExprArray {
 	pub fn new(ctx: Context, items: impl IntoIterator<Item = LocExpr>) -> Self {
-		Self(Cc::new(ExprArrayInner {
+		Self {
 			ctx,
-			cached: RefCell::new(items.into_iter().map(ArrayThunk::Waiting).collect()),
-		}))
+			cached: Cc::new(RefCell::new(
+				items.into_iter().map(ArrayThunk::Waiting).collect(),
+			)),
+		}
 	}
 }
 impl ArrayLike for ExprArray {
 	fn len(&self) -> usize {
-		self.0.cached.borrow().len()
+		self.cached.borrow().len()
 	}
 	fn get(&self, index: usize) -> Result<Option<Val>> {
 		if index >= self.len() {
 			return Ok(None);
 		}
-		match &self.0.cached.borrow()[index] {
+		match &self.cached.borrow()[index] {
 			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),
 			ArrayThunk::Errored(e) => return Err(e.clone()),
 			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
@@ -179,19 +171,19 @@
 		};
 
 		let ArrayThunk::Waiting(expr) =
-			replace(&mut self.0.cached.borrow_mut()[index], ArrayThunk::Pending)
+			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
 		else {
 			unreachable!()
 		};
 
-		let new_value = match evaluate(self.0.ctx.clone(), &expr) {
+		let new_value = match evaluate(self.ctx.clone(), &expr) {
 			Ok(v) => v,
 			Err(e) => {
-				self.0.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());
+				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());
 				return Err(e);
 			}
 		};
-		self.0.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());
+		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());
 		Ok(Some(new_value))
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
@@ -215,7 +207,7 @@
 		if index >= self.len() {
 			return None;
 		}
-		match &self.0.cached.borrow()[index] {
+		match &self.cached.borrow()[index] {
 			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
 			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
 			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
@@ -229,14 +221,12 @@
 	fn get_cheap(&self, _index: usize) -> Option<Val> {
 		None
 	}
-}
-impl From<ExprArray> for ArrValue {
-	fn from(value: ExprArray) -> Self {
-		Self::Expr(value)
+	fn is_cheap(&self) -> bool {
+		false
 	}
 }
 
-#[derive(Trace, Debug, Clone)]
+#[derive(Trace, Debug)]
 pub struct ExtendedArray {
 	pub a: ArrValue,
 	pub b: ArrValue,
@@ -319,15 +309,13 @@
 			self.b.get_cheap(index - self.split)
 		}
 	}
-}
-impl From<ExtendedArray> for ArrValue {
-	fn from(value: ExtendedArray) -> Self {
-		Self::Extended(Cc::new(value))
+	fn is_cheap(&self) -> bool {
+		self.a.is_cheap() && self.b.is_cheap()
 	}
 }
 
-#[derive(Trace, Debug, Clone)]
-pub struct LazyArray(pub Cc<Vec<Thunk<Val>>>);
+#[derive(Trace, Debug)]
+pub struct LazyArray(pub Vec<Thunk<Val>>);
 impl ArrayLike for LazyArray {
 	fn len(&self) -> usize {
 		self.0.len()
@@ -344,15 +332,13 @@
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
 		self.0.get(index).cloned()
 	}
-}
-impl From<LazyArray> for ArrValue {
-	fn from(value: LazyArray) -> Self {
-		Self::Lazy(value)
+	fn is_cheap(&self) -> bool {
+		false
 	}
 }
 
-#[derive(Trace, Debug, Clone)]
-pub struct EagerArray(pub Cc<Vec<Val>>);
+#[derive(Trace, Debug)]
+pub struct EagerArray(pub Vec<Val>);
 impl ArrayLike for EagerArray {
 	fn len(&self) -> usize {
 		self.0.len()
@@ -369,15 +355,13 @@
 	fn get_cheap(&self, index: usize) -> Option<Val> {
 		self.0.get(index).cloned()
 	}
-}
-impl From<EagerArray> for ArrValue {
-	fn from(value: EagerArray) -> Self {
-		Self::Eager(value)
+	fn is_cheap(&self) -> bool {
+		true
 	}
 }
 
 /// Inclusive range type
-#[derive(Debug, Trace, Clone, PartialEq, Eq)]
+#[derive(Debug, Trace, PartialEq, Eq)]
 pub struct RangeArray {
 	start: i32,
 	end: i32,
@@ -422,14 +406,12 @@
 	fn get_cheap(&self, index: usize) -> Option<Val> {
 		self.range().nth(index).map(|i| Val::Num(f64::from(i)))
 	}
-}
-impl From<RangeArray> for ArrValue {
-	fn from(value: RangeArray) -> Self {
-		Self::Range(value)
+	fn is_cheap(&self) -> bool {
+		true
 	}
 }
 
-#[derive(Debug, Trace, Clone)]
+#[derive(Debug, Trace)]
 pub struct ReverseArray(pub ArrValue);
 impl ArrayLike for ReverseArray {
 	fn len(&self) -> usize {
@@ -447,44 +429,37 @@
 	fn get_cheap(&self, index: usize) -> Option<Val> {
 		self.0.get_cheap(self.0.len() - index - 1)
 	}
-	fn reverse(self) -> ArrValue {
-		self.0
-	}
-}
-impl From<ReverseArray> for ArrValue {
-	fn from(value: ReverseArray) -> Self {
-		Self::Reverse(Cc::new(value))
+	fn is_cheap(&self) -> bool {
+		self.0.is_cheap()
 	}
 }
 
-#[derive(Trace, Debug)]
-pub struct MappedArrayInner {
+#[derive(Trace, Debug, Clone)]
+pub struct MappedArray {
 	inner: ArrValue,
-	cached: RefCell<Vec<ArrayThunk<()>>>,
+	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,
 	mapper: FuncVal,
 }
-#[derive(Trace, Debug, Clone)]
-pub struct MappedArray(Cc<MappedArrayInner>);
 impl MappedArray {
 	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {
 		let len = inner.len();
-		Self(Cc::new(MappedArrayInner {
+		Self {
 			inner,
-			cached: RefCell::new(vec![ArrayThunk::Waiting(()); len]),
+			cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting(()); len])),
 			mapper,
-		}))
+		}
 	}
 }
 impl ArrayLike for MappedArray {
 	fn len(&self) -> usize {
-		self.0.cached.borrow().len()
+		self.cached.borrow().len()
 	}
 
 	fn get(&self, index: usize) -> Result<Option<Val>> {
 		if index >= self.len() {
 			return Ok(None);
 		}
-		match &self.0.cached.borrow()[index] {
+		match &self.cached.borrow()[index] {
 			ArrayThunk::Computed(c) => return Ok(Some(c.clone())),
 			ArrayThunk::Errored(e) => return Err(e.clone()),
 			ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
@@ -492,27 +467,26 @@
 		};
 
 		let ArrayThunk::Waiting(_) =
-			replace(&mut self.0.cached.borrow_mut()[index], ArrayThunk::Pending)
+			replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
 		else {
 			unreachable!()
 		};
 
 		let val = self
-			.0
 			.inner
 			.get(index)
 			.transpose()
 			.expect("index checked")
-			.and_then(|r| self.0.mapper.evaluate_simple(&(r,), false));
+			.and_then(|r| self.mapper.evaluate_simple(&(r,), false));
 
 		let new_value = match val {
 			Ok(v) => v,
 			Err(e) => {
-				self.0.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());
+				self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());
 				return Err(e);
 			}
 		};
-		self.0.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());
+		self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());
 		Ok(Some(new_value))
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
@@ -536,7 +510,7 @@
 		if index >= self.len() {
 			return None;
 		}
-		match &self.0.cached.borrow()[index] {
+		match &self.cached.borrow()[index] {
 			ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
 			ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
 			ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
@@ -551,82 +525,54 @@
 	fn get_cheap(&self, _index: usize) -> Option<Val> {
 		None
 	}
-}
-impl From<MappedArray> for ArrValue {
-	fn from(value: MappedArray) -> Self {
-		Self::Mapped(value)
+	fn is_cheap(&self) -> bool {
+		false
 	}
 }
 
 #[derive(Trace, Debug)]
-pub struct RepeatedArrayInner {
+pub struct RepeatedArray {
 	data: ArrValue,
 	repeats: usize,
 	total_len: usize,
 }
-#[derive(Trace, Debug, Clone)]
-pub struct RepeatedArray(Cc<RepeatedArrayInner>);
 impl RepeatedArray {
 	pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {
 		let total_len = data.len().checked_mul(repeats)?;
-		Some(Self(Cc::new(RepeatedArrayInner {
+		Some(Self {
 			data,
 			repeats,
 			total_len,
-		})))
-	}
-	pub fn is_cheap(&self) -> bool {
-		self.0.data.is_cheap()
+		})
 	}
 }
 
 impl ArrayLike for RepeatedArray {
 	fn len(&self) -> usize {
-		self.0.total_len
+		self.total_len
 	}
 
 	fn get(&self, index: usize) -> Result<Option<Val>> {
-		if index > self.0.total_len {
+		if index > self.total_len {
 			return Ok(None);
 		}
-		self.0.data.get(index % self.0.data.len())
+		self.data.get(index % self.data.len())
 	}
 
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
-		if index > self.0.total_len {
+		if index > self.total_len {
 			return None;
 		}
-		self.0.data.get_lazy(index % self.0.data.len())
+		self.data.get_lazy(index % self.data.len())
 	}
 
 	fn get_cheap(&self, index: usize) -> Option<Val> {
-		if index > self.0.total_len {
+		if index > self.total_len {
 			return None;
 		}
-		self.0.data.get_cheap(index % self.0.data.len())
+		self.data.get_cheap(index % self.data.len())
 	}
-}
-impl From<RepeatedArray> for ArrValue {
-	fn from(value: RepeatedArray) -> Self {
-		Self::Repeated(value)
+	fn is_cheap(&self) -> bool {
+		self.data.is_cheap()
 	}
 }
-
-macro_rules! pass {
-	($t:ident.$m:ident($($ident:ident),*)) => {
-		match $t {
-			Self::Bytes(e) => e.$m($($ident)*),
-			Self::Chars(e) => e.$m($($ident)*),
-			Self::Expr(e) => e.$m($($ident)*),
-			Self::Lazy(e) => e.$m($($ident)*),
-			Self::Eager(e) => e.$m($($ident)*),
-			Self::Range(e) => e.$m($($ident)*),
-			Self::Slice(e) => e.$m($($ident)*),
-			Self::Extended(e) => e.$m($($ident)*),
-			Self::Reverse(e) => e.$m($($ident)*),
-			Self::Mapped(e) => e.$m($($ident)*),
-			Self::Repeated(e) => e.$m($($ident)*),
-		}
-	};
-}
-pub(super) use pass;
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -573,10 +573,10 @@
 						evaluate(self.ctx, &self.item)
 					}
 				}
-				Val::Arr(ArrValue::lazy(Cc::new(vec![Thunk::new(ArrayElement {
+				Val::Arr(ArrValue::lazy(vec![Thunk::new(ArrayElement {
 					ctx,
 					item: items[0].clone(),
-				})])))
+				})]))
 			} else {
 				Val::Arr(ArrValue::expr(ctx, items.iter().cloned()))
 			}
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -17,7 +17,7 @@
 	operator::evaluate_add_op,
 	tb, throw,
 	val::ThunkValue,
-	MaybeUnbound, Result, ResultExt, State, Thunk, Unbound, Val,
+	MaybeUnbound, Result, State, Thunk, Unbound, Val,
 };
 
 #[cfg(not(feature = "exp-preserve-order"))]
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -6,7 +6,7 @@
 use jrsonnet_types::{ComplexValType, ValType};
 
 use crate::{
-	arr::ArrValue,
+	arr::{ArrValue, BytesArray},
 	error::Result,
 	function::{native::NativeDesc, FuncDesc, FuncVal},
 	throw,
@@ -434,12 +434,13 @@
 	}
 
 	fn from_untyped(value: Val) -> Result<Self> {
-		if let Val::Arr(ArrValue::Bytes(bytes)) = value {
-			return Ok(bytes.0);
-		}
-		<Self as Typed>::TYPE.check(&value)?;
-		match value {
+		match &value {
 			Val::Arr(a) => {
+				if let Some(bytes) = a.as_any().downcast_ref::<BytesArray>() {
+					return Ok(bytes.0.as_slice().into());
+				};
+				<Self as Typed>::TYPE.check(&value)?;
+				// Any::downcast_ref::<ByteArray>(&a);
 				let mut out = Vec::with_capacity(a.len());
 				for e in a.iter() {
 					let r = e?;
@@ -447,7 +448,10 @@
 				}
 				Ok(out.as_slice().into())
 			}
-			_ => unreachable!(),
+			_ => {
+				<Self as Typed>::TYPE.check(&value)?;
+				unreachable!()
+			}
 		}
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14	error::{Error, ErrorKind::*},15	function::FuncVal,16	gc::{GcHashMap, TraceBox},17	manifest::{ManifestFormat, ToStringFormat},18	tb, throw,19	typed::BoundedUsize,20	ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24	type Output;25	fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30	Computed(T),31	Errored(Error),32	Waiting(TraceBox<dyn ThunkValue<Output = T>>),33	Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42	pub fn evaluated(val: T) -> Self {43		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44	}45	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47	}48	pub fn errored(e: Error) -> Self {49		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50	}51}5253impl<T> Thunk<T>54where55	T: Clone + Trace,56{57	pub fn force(&self) -> Result<()> {58		self.evaluate()?;59		Ok(())60	}6162	/// Evaluate thunk, or return cached value63	///64	/// # Errors65	///66	/// - Lazy value evaluation returned error67	/// - This method was called during inner value evaluation68	pub fn evaluate(&self) -> Result<T> {69		match &*self.0.borrow() {70			ThunkInner::Computed(v) => return Ok(v.clone()),71			ThunkInner::Errored(e) => return Err(e.clone()),72			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73			ThunkInner::Waiting(..) => (),74		};75		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)76		else {77			unreachable!();78		};79		let new_value = match value.0.get() {80			Ok(v) => v,81			Err(e) => {82				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());83				return Err(e);84			}85		};86		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());87		Ok(new_value)88	}89}9091pub trait ThunkMapper<Input>: Trace {92	type Output;93	fn map(self, from: Input) -> Result<Self::Output>;94}95impl<Input> Thunk<Input>96where97	Input: Trace + Clone,98{99	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>100	where101		M: ThunkMapper<Input>,102		M::Output: Trace,103	{104		#[derive(Trace)]105		struct Mapped<Input: Trace, Mapper: Trace> {106			inner: Thunk<Input>,107			mapper: Mapper,108		}109		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>110		where111			Input: Trace + Clone,112			Mapper: ThunkMapper<Input>,113		{114			type Output = Mapper::Output;115116			fn get(self: Box<Self>) -> Result<Self::Output> {117				let value = self.inner.evaluate()?;118				let mapped = self.mapper.map(value)?;119				Ok(mapped)120			}121		}122123		Thunk::new(Mapped::<Input, M> {124			inner: self,125			mapper,126		})127	}128}129130impl<T: Trace> From<Result<T>> for Thunk<T> {131	fn from(value: Result<T>) -> Self {132		match value {133			Ok(o) => Self::evaluated(o),134			Err(e) => Self::errored(e),135		}136	}137}138139impl<T: Trace + Default> Default for Thunk<T> {140	fn default() -> Self {141		Self::evaluated(T::default())142	}143}144145type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);146147#[derive(Trace, Clone)]148pub struct CachedUnbound<I, T>149where150	I: Unbound<Bound = T>,151	T: Trace,152{153	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,154	value: I,155}156impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {157	pub fn new(value: I) -> Self {158		Self {159			cache: Cc::new(RefCell::new(GcHashMap::new())),160			value,161		}162	}163}164impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {165	type Bound = T;166	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {167		let cache_key = (168			sup.as_ref().map(|s| s.clone().downgrade()),169			this.as_ref().map(|t| t.clone().downgrade()),170		);171		{172			if let Some(t) = self.cache.borrow().get(&cache_key) {173				return Ok(t.clone());174			}175		}176		let bound = self.value.bind(sup, this)?;177178		{179			let mut cache = self.cache.borrow_mut();180			cache.insert(cache_key, bound.clone());181		}182183		Ok(bound)184	}185}186187impl<T: Debug + Trace> Debug for Thunk<T> {188	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {189		write!(f, "Lazy")190	}191}192impl<T: Trace> PartialEq for Thunk<T> {193	fn eq(&self, other: &Self) -> bool {194		Cc::ptr_eq(&self.0, &other.0)195	}196}197198/// Represents a Jsonnet value, which can be sliced or indexed (string or array).199#[allow(clippy::module_name_repetitions)]200pub enum IndexableVal {201	/// String.202	Str(IStr),203	/// Array.204	Arr(ArrValue),205}206impl IndexableVal {207	pub fn to_array(self) -> ArrValue {208		match self {209			IndexableVal::Str(s) => ArrValue::chars(s.chars()),210			IndexableVal::Arr(arr) => arr,211		}212	}213	/// Slice the value.214	///215	/// # Implementation216	///217	/// For strings, will create a copy of specified interval.218	///219	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.220	pub fn slice(221		self,222		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,223		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,224		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,225	) -> Result<Self> {226		match &self {227			IndexableVal::Str(s) => {228				let index = index.as_deref().copied().unwrap_or(0);229				let end = end.as_deref().copied().unwrap_or(usize::MAX);230				let step = step.as_deref().copied().unwrap_or(1);231232				if index >= end {233					return Ok(Self::Str("".into()));234				}235236				Ok(Self::Str(237					(s.chars()238						.skip(index)239						.take(end - index)240						.step_by(step)241						.collect::<String>())242					.into(),243				))244			}245			IndexableVal::Arr(arr) => {246				let index = index.as_deref().copied().unwrap_or(0);247				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());248				let step = step.as_deref().copied().unwrap_or(1);249250				if index >= end {251					return Ok(Self::Arr(ArrValue::empty()));252				}253254				Ok(Self::Arr(255					arr.clone()256						.slice(Some(index), Some(end), Some(step))257						.expect("arguments checked"),258				))259			}260		}261	}262}263264#[derive(Debug, Clone, Trace)]265pub enum StrValue {266	Flat(IStr),267	Tree(Rc<(StrValue, StrValue, usize)>),268}269impl StrValue {270	pub fn concat(a: StrValue, b: StrValue) -> Self {271		// TODO: benchmark for an optimal value, currently just a arbitrary choice272		const STRING_EXTEND_THRESHOLD: usize = 100;273274		if a.is_empty() {275			b276		} else if b.is_empty() {277			a278		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {279			Self::Flat(format!("{a}{b}").into())280		} else {281			let len = a.len() + b.len();282			Self::Tree(Rc::new((a, b, len)))283		}284	}285	pub fn into_flat(self) -> IStr {286		#[cold]287		fn write_buf(s: &StrValue, out: &mut String) {288			match s {289				StrValue::Flat(f) => out.push_str(f),290				StrValue::Tree(t) => {291					write_buf(&t.0, out);292					write_buf(&t.1, out);293				}294			}295		}296		match self {297			StrValue::Flat(f) => f,298			StrValue::Tree(_) => {299				let mut buf = String::with_capacity(self.len());300				write_buf(&self, &mut buf);301				buf.into()302			}303		}304	}305	pub fn len(&self) -> usize {306		match self {307			StrValue::Flat(v) => v.len(),308			StrValue::Tree(t) => t.2,309		}310	}311	pub fn is_empty(&self) -> bool {312		match self {313			Self::Flat(v) => v.is_empty(),314			// Can't create non-flat empty string315			Self::Tree(_) => false,316		}317	}318}319impl From<&str> for StrValue {320	fn from(value: &str) -> Self {321		Self::Flat(value.into())322	}323}324impl From<String> for StrValue {325	fn from(value: String) -> Self {326		Self::Flat(value.into())327	}328}329impl From<IStr> for StrValue {330	fn from(value: IStr) -> Self {331		Self::Flat(value)332	}333}334impl Display for StrValue {335	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {336		match self {337			StrValue::Flat(v) => write!(f, "{v}"),338			StrValue::Tree(t) => {339				write!(f, "{}", t.0)?;340				write!(f, "{}", t.1)341			}342		}343	}344}345impl PartialEq for StrValue {346	fn eq(&self, other: &Self) -> bool {347		let a = self.clone().into_flat();348		let b = other.clone().into_flat();349		a == b350	}351}352impl Eq for StrValue {}353impl PartialOrd for StrValue {354	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {355		Some(self.cmp(other))356	}357}358impl Ord for StrValue {359	fn cmp(&self, other: &Self) -> std::cmp::Ordering {360		let a = self.clone().into_flat();361		let b = other.clone().into_flat();362		a.cmp(&b)363	}364}365366/// Represents any valid Jsonnet value.367#[derive(Debug, Clone, Trace, Default)]368pub enum Val {369	/// Represents a Jsonnet boolean.370	Bool(bool),371	/// Represents a Jsonnet null value.372	#[default]373	Null,374	/// Represents a Jsonnet string.375	Str(StrValue),376	/// Represents a Jsonnet number.377	/// Should be finite, and not NaN378	/// This restriction isn't enforced by enum, as enum field can't be marked as private379	Num(f64),380	/// Experimental bigint381	#[cfg(feature = "exp-bigint")]382	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),383	/// Represents a Jsonnet array.384	Arr(ArrValue),385	/// Represents a Jsonnet object.386	Obj(ObjValue),387	/// Represents a Jsonnet function.388	Func(FuncVal),389}390391#[cfg(target_pointer_width = "64")]392static_assertions::assert_eq_size!(Val, [u8; 24]);393394impl From<IndexableVal> for Val {395	fn from(v: IndexableVal) -> Self {396		match v {397			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),398			IndexableVal::Arr(a) => Self::Arr(a),399		}400	}401}402403impl Val {404	pub const fn as_bool(&self) -> Option<bool> {405		match self {406			Self::Bool(v) => Some(*v),407			_ => None,408		}409	}410	pub const fn as_null(&self) -> Option<()> {411		match self {412			Self::Null => Some(()),413			_ => None,414		}415	}416	pub fn as_str(&self) -> Option<IStr> {417		match self {418			Self::Str(s) => Some(s.clone().into_flat()),419			_ => None,420		}421	}422	pub const fn as_num(&self) -> Option<f64> {423		match self {424			Self::Num(n) => Some(*n),425			_ => None,426		}427	}428	pub fn as_arr(&self) -> Option<ArrValue> {429		match self {430			Self::Arr(a) => Some(a.clone()),431			_ => None,432		}433	}434	pub fn as_obj(&self) -> Option<ObjValue> {435		match self {436			Self::Obj(o) => Some(o.clone()),437			_ => None,438		}439	}440	pub fn as_func(&self) -> Option<FuncVal> {441		match self {442			Self::Func(f) => Some(f.clone()),443			_ => None,444		}445	}446447	/// Creates `Val::Num` after checking for numeric overflow.448	/// As numbers are `f64`, we can just check for their finity.449	pub fn new_checked_num(num: f64) -> Result<Self> {450		if num.is_finite() {451			Ok(Self::Num(num))452		} else {453			throw!("overflow")454		}455	}456457	pub const fn value_type(&self) -> ValType {458		match self {459			Self::Str(..) => ValType::Str,460			Self::Num(..) => ValType::Num,461			#[cfg(feature = "exp-bigint")]462			Self::BigInt(..) => ValType::BigInt,463			Self::Arr(..) => ValType::Arr,464			Self::Obj(..) => ValType::Obj,465			Self::Bool(_) => ValType::Bool,466			Self::Null => ValType::Null,467			Self::Func(..) => ValType::Func,468		}469	}470471	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {472		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {473			manifest.manifest(val.clone())474		}475		manifest_dyn(self, &format)476	}477478	pub fn to_string(&self) -> Result<IStr> {479		Ok(match self {480			Self::Bool(true) => "true".into(),481			Self::Bool(false) => "false".into(),482			Self::Null => "null".into(),483			Self::Str(s) => s.clone().into_flat(),484			_ => self.manifest(ToStringFormat).map(IStr::from)?,485		})486	}487488	pub fn into_indexable(self) -> Result<IndexableVal> {489		Ok(match self {490			Val::Str(s) => IndexableVal::Str(s.into_flat()),491			Val::Arr(arr) => IndexableVal::Arr(arr),492			_ => throw!(ValueIsNotIndexable(self.value_type())),493		})494	}495}496497const fn is_function_like(val: &Val) -> bool {498	matches!(val, Val::Func(_))499}500501/// Native implementation of `std.primitiveEquals`502pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {503	Ok(match (val_a, val_b) {504		(Val::Bool(a), Val::Bool(b)) => a == b,505		(Val::Null, Val::Null) => true,506		(Val::Str(a), Val::Str(b)) => a == b,507		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,508		#[cfg(feature = "exp-bigint")]509		(Val::BigInt(a), Val::BigInt(b)) => a == b,510		(Val::Arr(_), Val::Arr(_)) => {511			throw!("primitiveEquals operates on primitive types, got array")512		}513		(Val::Obj(_), Val::Obj(_)) => {514			throw!("primitiveEquals operates on primitive types, got object")515		}516		(a, b) if is_function_like(a) && is_function_like(b) => {517			throw!("cannot test equality of functions")518		}519		(_, _) => false,520	})521}522523/// Native implementation of `std.equals`524pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {525	if val_a.value_type() != val_b.value_type() {526		return Ok(false);527	}528	match (val_a, val_b) {529		(Val::Arr(a), Val::Arr(b)) => {530			if ArrValue::ptr_eq(a, b) {531				return Ok(true);532			}533			if a.len() != b.len() {534				return Ok(false);535			}536			for (a, b) in a.iter().zip(b.iter()) {537				if !equals(&a?, &b?)? {538					return Ok(false);539				}540			}541			Ok(true)542		}543		(Val::Obj(a), Val::Obj(b)) => {544			if ObjValue::ptr_eq(a, b) {545				return Ok(true);546			}547			let fields = a.fields(548				#[cfg(feature = "exp-preserve-order")]549				false,550			);551			if fields552				!= b.fields(553					#[cfg(feature = "exp-preserve-order")]554					false,555				) {556				return Ok(false);557			}558			for field in fields {559				if !equals(560					&a.get(field.clone())?.expect("field exists"),561					&b.get(field)?.expect("field exists"),562				)? {563					return Ok(false);564				}565			}566			Ok(true)567		}568		(a, b) => Ok(primitive_equals(a, b)?),569	}570}
after · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	fmt::{self, Debug, Display},4	mem::replace,5	rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::{ArrValue, ArrayLike};13use crate::{14	error::{Error, ErrorKind::*},15	function::FuncVal,16	gc::{GcHashMap, TraceBox},17	manifest::{ManifestFormat, ToStringFormat},18	tb, throw,19	typed::BoundedUsize,20	ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24	type Output;25	fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30	Computed(T),31	Errored(Error),32	Waiting(TraceBox<dyn ThunkValue<Output = T>>),33	Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42	pub fn evaluated(val: T) -> Self {43		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44	}45	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47	}48	pub fn errored(e: Error) -> Self {49		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50	}51}5253impl<T> Thunk<T>54where55	T: Clone + Trace,56{57	pub fn force(&self) -> Result<()> {58		self.evaluate()?;59		Ok(())60	}6162	/// Evaluate thunk, or return cached value63	///64	/// # Errors65	///66	/// - Lazy value evaluation returned error67	/// - This method was called during inner value evaluation68	pub fn evaluate(&self) -> Result<T> {69		match &*self.0.borrow() {70			ThunkInner::Computed(v) => return Ok(v.clone()),71			ThunkInner::Errored(e) => return Err(e.clone()),72			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73			ThunkInner::Waiting(..) => (),74		};75		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)76		else {77			unreachable!();78		};79		let new_value = match value.0.get() {80			Ok(v) => v,81			Err(e) => {82				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());83				return Err(e);84			}85		};86		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());87		Ok(new_value)88	}89}9091pub trait ThunkMapper<Input>: Trace {92	type Output;93	fn map(self, from: Input) -> Result<Self::Output>;94}95impl<Input> Thunk<Input>96where97	Input: Trace + Clone,98{99	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>100	where101		M: ThunkMapper<Input>,102		M::Output: Trace,103	{104		#[derive(Trace)]105		struct Mapped<Input: Trace, Mapper: Trace> {106			inner: Thunk<Input>,107			mapper: Mapper,108		}109		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>110		where111			Input: Trace + Clone,112			Mapper: ThunkMapper<Input>,113		{114			type Output = Mapper::Output;115116			fn get(self: Box<Self>) -> Result<Self::Output> {117				let value = self.inner.evaluate()?;118				let mapped = self.mapper.map(value)?;119				Ok(mapped)120			}121		}122123		Thunk::new(Mapped::<Input, M> {124			inner: self,125			mapper,126		})127	}128}129130impl<T: Trace> From<Result<T>> for Thunk<T> {131	fn from(value: Result<T>) -> Self {132		match value {133			Ok(o) => Self::evaluated(o),134			Err(e) => Self::errored(e),135		}136	}137}138139impl<T: Trace + Default> Default for Thunk<T> {140	fn default() -> Self {141		Self::evaluated(T::default())142	}143}144145type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);146147#[derive(Trace, Clone)]148pub struct CachedUnbound<I, T>149where150	I: Unbound<Bound = T>,151	T: Trace,152{153	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,154	value: I,155}156impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {157	pub fn new(value: I) -> Self {158		Self {159			cache: Cc::new(RefCell::new(GcHashMap::new())),160			value,161		}162	}163}164impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {165	type Bound = T;166	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {167		let cache_key = (168			sup.as_ref().map(|s| s.clone().downgrade()),169			this.as_ref().map(|t| t.clone().downgrade()),170		);171		{172			if let Some(t) = self.cache.borrow().get(&cache_key) {173				return Ok(t.clone());174			}175		}176		let bound = self.value.bind(sup, this)?;177178		{179			let mut cache = self.cache.borrow_mut();180			cache.insert(cache_key, bound.clone());181		}182183		Ok(bound)184	}185}186187impl<T: Debug + Trace> Debug for Thunk<T> {188	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {189		write!(f, "Lazy")190	}191}192impl<T: Trace> PartialEq for Thunk<T> {193	fn eq(&self, other: &Self) -> bool {194		Cc::ptr_eq(&self.0, &other.0)195	}196}197198/// Represents a Jsonnet value, which can be sliced or indexed (string or array).199#[allow(clippy::module_name_repetitions)]200pub enum IndexableVal {201	/// String.202	Str(IStr),203	/// Array.204	Arr(ArrValue),205}206impl IndexableVal {207	pub fn to_array(self) -> ArrValue {208		match self {209			IndexableVal::Str(s) => ArrValue::chars(s.chars()),210			IndexableVal::Arr(arr) => arr,211		}212	}213	/// Slice the value.214	///215	/// # Implementation216	///217	/// For strings, will create a copy of specified interval.218	///219	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.220	pub fn slice(221		self,222		index: Option<BoundedUsize<0, { i32::MAX as usize }>>,223		end: Option<BoundedUsize<0, { i32::MAX as usize }>>,224		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,225	) -> Result<Self> {226		match &self {227			IndexableVal::Str(s) => {228				let index = index.as_deref().copied().unwrap_or(0);229				let end = end.as_deref().copied().unwrap_or(usize::MAX);230				let step = step.as_deref().copied().unwrap_or(1);231232				if index >= end {233					return Ok(Self::Str("".into()));234				}235236				Ok(Self::Str(237					(s.chars()238						.skip(index)239						.take(end - index)240						.step_by(step)241						.collect::<String>())242					.into(),243				))244			}245			IndexableVal::Arr(arr) => {246				let index = index.as_deref().copied().unwrap_or(0);247				let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());248				let step = step.as_deref().copied().unwrap_or(1);249250				if index >= end {251					return Ok(Self::Arr(ArrValue::empty()));252				}253254				Ok(Self::Arr(255					arr.clone()256						.slice(Some(index), Some(end), Some(step))257						.expect("arguments checked"),258				))259			}260		}261	}262}263264#[derive(Debug, Clone, Trace)]265pub enum StrValue {266	Flat(IStr),267	Tree(Rc<(StrValue, StrValue, usize)>),268}269impl StrValue {270	pub fn concat(a: StrValue, b: StrValue) -> Self {271		// TODO: benchmark for an optimal value, currently just a arbitrary choice272		const STRING_EXTEND_THRESHOLD: usize = 100;273274		if a.is_empty() {275			b276		} else if b.is_empty() {277			a278		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {279			Self::Flat(format!("{a}{b}").into())280		} else {281			let len = a.len() + b.len();282			Self::Tree(Rc::new((a, b, len)))283		}284	}285	pub fn into_flat(self) -> IStr {286		#[cold]287		fn write_buf(s: &StrValue, out: &mut String) {288			match s {289				StrValue::Flat(f) => out.push_str(f),290				StrValue::Tree(t) => {291					write_buf(&t.0, out);292					write_buf(&t.1, out);293				}294			}295		}296		match self {297			StrValue::Flat(f) => f,298			StrValue::Tree(_) => {299				let mut buf = String::with_capacity(self.len());300				write_buf(&self, &mut buf);301				buf.into()302			}303		}304	}305	pub fn len(&self) -> usize {306		match self {307			StrValue::Flat(v) => v.len(),308			StrValue::Tree(t) => t.2,309		}310	}311	pub fn is_empty(&self) -> bool {312		match self {313			Self::Flat(v) => v.is_empty(),314			// Can't create non-flat empty string315			Self::Tree(_) => false,316		}317	}318}319impl From<&str> for StrValue {320	fn from(value: &str) -> Self {321		Self::Flat(value.into())322	}323}324impl From<String> for StrValue {325	fn from(value: String) -> Self {326		Self::Flat(value.into())327	}328}329impl From<IStr> for StrValue {330	fn from(value: IStr) -> Self {331		Self::Flat(value)332	}333}334impl Display for StrValue {335	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {336		match self {337			StrValue::Flat(v) => write!(f, "{v}"),338			StrValue::Tree(t) => {339				write!(f, "{}", t.0)?;340				write!(f, "{}", t.1)341			}342		}343	}344}345impl PartialEq for StrValue {346	fn eq(&self, other: &Self) -> bool {347		let a = self.clone().into_flat();348		let b = other.clone().into_flat();349		a == b350	}351}352impl Eq for StrValue {}353impl PartialOrd for StrValue {354	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {355		Some(self.cmp(other))356	}357}358impl Ord for StrValue {359	fn cmp(&self, other: &Self) -> std::cmp::Ordering {360		let a = self.clone().into_flat();361		let b = other.clone().into_flat();362		a.cmp(&b)363	}364}365366/// Represents any valid Jsonnet value.367#[derive(Debug, Clone, Trace, Default)]368pub enum Val {369	/// Represents a Jsonnet boolean.370	Bool(bool),371	/// Represents a Jsonnet null value.372	#[default]373	Null,374	/// Represents a Jsonnet string.375	Str(StrValue),376	/// Represents a Jsonnet number.377	/// Should be finite, and not NaN378	/// This restriction isn't enforced by enum, as enum field can't be marked as private379	Num(f64),380	/// Experimental bigint381	#[cfg(feature = "exp-bigint")]382	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),383	/// Represents a Jsonnet array.384	Arr(ArrValue),385	/// Represents a Jsonnet object.386	Obj(ObjValue),387	/// Represents a Jsonnet function.388	Func(FuncVal),389}390391#[cfg(target_pointer_width = "64")]392static_assertions::assert_eq_size!(Val, [u8; 24]);393394impl From<IndexableVal> for Val {395	fn from(v: IndexableVal) -> Self {396		match v {397			IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),398			IndexableVal::Arr(a) => Self::Arr(a),399		}400	}401}402403impl Val {404	pub const fn as_bool(&self) -> Option<bool> {405		match self {406			Self::Bool(v) => Some(*v),407			_ => None,408		}409	}410	pub const fn as_null(&self) -> Option<()> {411		match self {412			Self::Null => Some(()),413			_ => None,414		}415	}416	pub fn as_str(&self) -> Option<IStr> {417		match self {418			Self::Str(s) => Some(s.clone().into_flat()),419			_ => None,420		}421	}422	pub const fn as_num(&self) -> Option<f64> {423		match self {424			Self::Num(n) => Some(*n),425			_ => None,426		}427	}428	pub fn as_arr(&self) -> Option<ArrValue> {429		match self {430			Self::Arr(a) => Some(a.clone()),431			_ => None,432		}433	}434	pub fn as_obj(&self) -> Option<ObjValue> {435		match self {436			Self::Obj(o) => Some(o.clone()),437			_ => None,438		}439	}440	pub fn as_func(&self) -> Option<FuncVal> {441		match self {442			Self::Func(f) => Some(f.clone()),443			_ => None,444		}445	}446447	/// Creates `Val::Num` after checking for numeric overflow.448	/// As numbers are `f64`, we can just check for their finity.449	pub fn new_checked_num(num: f64) -> Result<Self> {450		if num.is_finite() {451			Ok(Self::Num(num))452		} else {453			throw!("overflow")454		}455	}456457	pub const fn value_type(&self) -> ValType {458		match self {459			Self::Str(..) => ValType::Str,460			Self::Num(..) => ValType::Num,461			#[cfg(feature = "exp-bigint")]462			Self::BigInt(..) => ValType::BigInt,463			Self::Arr(..) => ValType::Arr,464			Self::Obj(..) => ValType::Obj,465			Self::Bool(_) => ValType::Bool,466			Self::Null => ValType::Null,467			Self::Func(..) => ValType::Func,468		}469	}470471	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {472		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {473			manifest.manifest(val.clone())474		}475		manifest_dyn(self, &format)476	}477478	pub fn to_string(&self) -> Result<IStr> {479		Ok(match self {480			Self::Bool(true) => "true".into(),481			Self::Bool(false) => "false".into(),482			Self::Null => "null".into(),483			Self::Str(s) => s.clone().into_flat(),484			_ => self.manifest(ToStringFormat).map(IStr::from)?,485		})486	}487488	pub fn into_indexable(self) -> Result<IndexableVal> {489		Ok(match self {490			Val::Str(s) => IndexableVal::Str(s.into_flat()),491			Val::Arr(arr) => IndexableVal::Arr(arr),492			_ => throw!(ValueIsNotIndexable(self.value_type())),493		})494	}495}496497const fn is_function_like(val: &Val) -> bool {498	matches!(val, Val::Func(_))499}500501/// Native implementation of `std.primitiveEquals`502pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {503	Ok(match (val_a, val_b) {504		(Val::Bool(a), Val::Bool(b)) => a == b,505		(Val::Null, Val::Null) => true,506		(Val::Str(a), Val::Str(b)) => a == b,507		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,508		#[cfg(feature = "exp-bigint")]509		(Val::BigInt(a), Val::BigInt(b)) => a == b,510		(Val::Arr(_), Val::Arr(_)) => {511			throw!("primitiveEquals operates on primitive types, got array")512		}513		(Val::Obj(_), Val::Obj(_)) => {514			throw!("primitiveEquals operates on primitive types, got object")515		}516		(a, b) if is_function_like(a) && is_function_like(b) => {517			throw!("cannot test equality of functions")518		}519		(_, _) => false,520	})521}522523/// Native implementation of `std.equals`524pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {525	if val_a.value_type() != val_b.value_type() {526		return Ok(false);527	}528	match (val_a, val_b) {529		(Val::Arr(a), Val::Arr(b)) => {530			if ArrValue::ptr_eq(a, b) {531				return Ok(true);532			}533			if a.len() != b.len() {534				return Ok(false);535			}536			for (a, b) in a.iter().zip(b.iter()) {537				if !equals(&a?, &b?)? {538					return Ok(false);539				}540			}541			Ok(true)542		}543		(Val::Obj(a), Val::Obj(b)) => {544			if ObjValue::ptr_eq(a, b) {545				return Ok(true);546			}547			let fields = a.fields(548				#[cfg(feature = "exp-preserve-order")]549				false,550			);551			if fields552				!= b.fields(553					#[cfg(feature = "exp-preserve-order")]554					false,555				) {556				return Ok(false);557			}558			for field in fields {559				if !equals(560					&a.get(field.clone())?.expect("field exists"),561					&b.get(field)?.expect("field exists"),562				)? {563					return Ok(false);564				}565			}566			Ok(true)567		}568		(a, b) => Ok(primitive_equals(a, b)?),569	}570}
modifiedcrates/jrsonnet-stdlib/src/sets.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sets.rs
+++ b/crates/jrsonnet-stdlib/src/sets.rs
@@ -7,7 +7,6 @@
 	val::ArrValue,
 	Thunk, Val,
 };
-use jrsonnet_gcmodule::Cc;
 use jrsonnet_parser::BinaryOpType;
 
 #[builtin]
@@ -70,5 +69,5 @@
 			}
 		};
 	}
-	Ok(ArrValue::lazy(Cc::new(out)))
+	Ok(ArrValue::lazy(out))
 }
modifiedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -10,7 +10,6 @@
 	val::{equals, ArrValue},
 	Thunk, Val,
 };
-use jrsonnet_gcmodule::Cc;
 use jrsonnet_parser::BinaryOpType;
 
 use crate::eval_on_empty;
@@ -136,7 +135,7 @@
 			values.iter().collect::<Result<Vec<Val>>>()?,
 		)?))
 	} else {
-		Ok(ArrValue::lazy(Cc::new(sort_keyf(values, key_getter)?)))
+		Ok(ArrValue::lazy(sort_keyf(values, key_getter)?))
 	}
 }
 
@@ -186,7 +185,7 @@
 			arr.iter().collect::<Result<Vec<Val>>>()?,
 		)?))
 	} else {
-		Ok(ArrValue::lazy(Cc::new(uniq_keyf(arr, keyF)?)))
+		Ok(ArrValue::lazy(uniq_keyf(arr, keyF)?))
 	}
 }
 
@@ -204,8 +203,8 @@
 		Ok(ArrValue::eager(arr))
 	} else {
 		let arr = sort_keyf(arr, keyF.clone())?;
-		let arr = uniq_keyf(ArrValue::lazy(Cc::new(arr)), keyF)?;
-		Ok(ArrValue::lazy(Cc::new(arr)))
+		let arr = uniq_keyf(ArrValue::lazy(arr), keyF)?;
+		Ok(ArrValue::lazy(arr))
 	}
 }