git.delta.rocks / jrsonnet / refs/commits / 8d39c73a8cfc

difftreelog

perf inline NumValue methods

Yaroslav Bolyukin2024-05-27parent: #afca77d.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -94,7 +94,7 @@
 	use Val::*;
 	Ok(match (a, b) {
 		(Str(a), Str(b)) => a.cmp(b),
-		(Num(a), Num(b)) => a.partial_cmp(b).expect("jsonnet numbers are non NaN"),
+		(Num(a), Num(b)) => a.cmp(b),
 		#[cfg(feature = "exp-bigint")]
 		(BigInt(a), BigInt(b)) => a.cmp(b),
 		(Arr(a), Arr(b)) => {
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	cmp::Ordering,4	fmt::{self, Debug, Display},5	mem::replace,6	num::NonZeroU32,7	ops::Deref,8	rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14use jrsonnet_types::ValType;15use thiserror::Error;1617pub use crate::arr::{ArrValue, ArrayLike};18use crate::{19	bail,20	error::{Error, ErrorKind::*},21	function::FuncVal,22	gc::{GcHashMap, TraceBox},23	manifest::{ManifestFormat, ToStringFormat},24	tb,25	typed::BoundedUsize,26	ObjValue, Result, Unbound, WeakObjValue,27};2829pub trait ThunkValue: Trace {30	type Output;31	fn get(self: Box<Self>) -> Result<Self::Output>;32}3334#[derive(Trace)]35enum ThunkInner<T: Trace> {36	Computed(T),37	Errored(Error),38	Waiting(TraceBox<dyn ThunkValue<Output = T>>),39	Pending,40}4142/// Lazily evaluated value43#[allow(clippy::module_name_repetitions)]44#[derive(Clone, Trace)]45pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4647impl<T: Trace> Thunk<T> {48	pub fn evaluated(val: T) -> Self {49		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))50	}51	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {52		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))53	}54	pub fn errored(e: Error) -> Self {55		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))56	}57	pub fn result(res: Result<T, Error>) -> Self {58		match res {59			Ok(o) => Self::evaluated(o),60			Err(e) => Self::errored(e),61		}62	}63}6465impl<T> Thunk<T>66where67	T: Clone + Trace,68{69	pub fn force(&self) -> Result<()> {70		self.evaluate()?;71		Ok(())72	}7374	/// Evaluate thunk, or return cached value75	///76	/// # Errors77	///78	/// - Lazy value evaluation returned error79	/// - This method was called during inner value evaluation80	pub fn evaluate(&self) -> Result<T> {81		match &*self.0.borrow() {82			ThunkInner::Computed(v) => return Ok(v.clone()),83			ThunkInner::Errored(e) => return Err(e.clone()),84			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),85			ThunkInner::Waiting(..) => (),86		};87		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)88		else {89			unreachable!();90		};91		let new_value = match value.0.get() {92			Ok(v) => v,93			Err(e) => {94				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());95				return Err(e);96			}97		};98		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());99		Ok(new_value)100	}101}102103pub trait ThunkMapper<Input>: Trace {104	type Output;105	fn map(self, from: Input) -> Result<Self::Output>;106}107impl<Input> Thunk<Input>108where109	Input: Trace + Clone,110{111	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>112	where113		M: ThunkMapper<Input>,114		M::Output: Trace,115	{116		#[derive(Trace)]117		struct Mapped<Input: Trace, Mapper: Trace> {118			inner: Thunk<Input>,119			mapper: Mapper,120		}121		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>122		where123			Input: Trace + Clone,124			Mapper: ThunkMapper<Input>,125		{126			type Output = Mapper::Output;127128			fn get(self: Box<Self>) -> Result<Self::Output> {129				let value = self.inner.evaluate()?;130				let mapped = self.mapper.map(value)?;131				Ok(mapped)132			}133		}134135		Thunk::new(Mapped::<Input, M> {136			inner: self,137			mapper,138		})139	}140}141142impl<T: Trace> From<Result<T>> for Thunk<T> {143	fn from(value: Result<T>) -> Self {144		match value {145			Ok(o) => Self::evaluated(o),146			Err(e) => Self::errored(e),147		}148	}149}150impl<T, V: Trace> From<T> for Thunk<V>151where152	T: ThunkValue<Output = V>,153{154	fn from(value: T) -> Self {155		Self::new(value)156	}157}158159impl<T: Trace + Default> Default for Thunk<T> {160	fn default() -> Self {161		Self::evaluated(T::default())162	}163}164165type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);166167#[derive(Trace, Clone)]168pub struct CachedUnbound<I, T>169where170	I: Unbound<Bound = T>,171	T: Trace,172{173	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,174	value: I,175}176impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {177	pub fn new(value: I) -> Self {178		Self {179			cache: Cc::new(RefCell::new(GcHashMap::new())),180			value,181		}182	}183}184impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {185	type Bound = T;186	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {187		let cache_key = (188			sup.as_ref().map(|s| s.clone().downgrade()),189			this.as_ref().map(|t| t.clone().downgrade()),190		);191		{192			if let Some(t) = self.cache.borrow().get(&cache_key) {193				return Ok(t.clone());194			}195		}196		let bound = self.value.bind(sup, this)?;197198		{199			let mut cache = self.cache.borrow_mut();200			cache.insert(cache_key, bound.clone());201		}202203		Ok(bound)204	}205}206207impl<T: Debug + Trace> Debug for Thunk<T> {208	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {209		write!(f, "Lazy")210	}211}212impl<T: Trace> PartialEq for Thunk<T> {213	fn eq(&self, other: &Self) -> bool {214		Cc::ptr_eq(&self.0, &other.0)215	}216}217218/// Represents a Jsonnet value, which can be sliced or indexed (string or array).219#[allow(clippy::module_name_repetitions)]220pub enum IndexableVal {221	/// String.222	Str(IStr),223	/// Array.224	Arr(ArrValue),225}226impl IndexableVal {227	pub fn is_empty(&self) -> bool {228		match self {229			Self::Str(s) => s.is_empty(),230			Self::Arr(s) => s.is_empty(),231		}232	}233234	pub fn to_array(self) -> ArrValue {235		match self {236			Self::Str(s) => ArrValue::chars(s.chars()),237			Self::Arr(arr) => arr,238		}239	}240	/// Slice the value.241	///242	/// # Implementation243	///244	/// For strings, will create a copy of specified interval.245	///246	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.247	pub fn slice(248		self,249		index: Option<i32>,250		end: Option<i32>,251		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,252	) -> Result<Self> {253		match &self {254			Self::Str(s) => {255				let mut computed_len = None;256				let mut get_len = || {257					computed_len.map_or_else(258						|| {259							let len = s.chars().count();260							let _ = computed_len.insert(len);261							len262						},263						|len| len,264					)265				};266				let mut get_idx = |pos: Option<i32>, default| {267					match pos {268						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),269						// No need to clamp, as iterator interface is used270						Some(v) => v as usize,271						None => default,272					}273				};274275				let index = get_idx(index, 0);276				let end = get_idx(end, usize::MAX);277				let step = step.as_deref().copied().unwrap_or(1);278279				if index >= end {280					return Ok(Self::Str("".into()));281				}282283				Ok(Self::Str(284					(s.chars()285						.skip(index)286						.take(end - index)287						.step_by(step)288						.collect::<String>())289					.into(),290				))291			}292			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(293				index,294				end,295				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),296			))),297		}298	}299}300301#[derive(Debug, Clone, Trace)]302pub enum StrValue {303	Flat(IStr),304	Tree(Rc<(StrValue, StrValue, usize)>),305}306impl StrValue {307	pub fn concat(a: Self, b: Self) -> Self {308		// TODO: benchmark for an optimal value, currently just a arbitrary choice309		const STRING_EXTEND_THRESHOLD: usize = 100;310311		if a.is_empty() {312			b313		} else if b.is_empty() {314			a315		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {316			Self::Flat(format!("{a}{b}").into())317		} else {318			let len = a.len() + b.len();319			Self::Tree(Rc::new((a, b, len)))320		}321	}322	pub fn into_flat(self) -> IStr {323		#[cold]324		fn write_buf(s: &StrValue, out: &mut String) {325			match s {326				StrValue::Flat(f) => out.push_str(f),327				StrValue::Tree(t) => {328					write_buf(&t.0, out);329					write_buf(&t.1, out);330				}331			}332		}333		match self {334			Self::Flat(f) => f,335			Self::Tree(_) => {336				let mut buf = String::with_capacity(self.len());337				write_buf(&self, &mut buf);338				buf.into()339			}340		}341	}342	pub fn len(&self) -> usize {343		match self {344			Self::Flat(v) => v.len(),345			Self::Tree(t) => t.2,346		}347	}348	pub fn is_empty(&self) -> bool {349		match self {350			Self::Flat(v) => v.is_empty(),351			// Can't create non-flat empty string352			Self::Tree(_) => false,353		}354	}355}356impl<T> From<T> for StrValue357where358	IStr: From<T>,359{360	fn from(value: T) -> Self {361		Self::Flat(IStr::from(value))362	}363}364impl Display for StrValue {365	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {366		match self {367			Self::Flat(v) => write!(f, "{v}"),368			Self::Tree(t) => {369				write!(f, "{}", t.0)?;370				write!(f, "{}", t.1)371			}372		}373	}374}375impl PartialEq for StrValue {376	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.377	#[allow(clippy::unconditional_recursion)]378	fn eq(&self, other: &Self) -> bool {379		let a = self.clone().into_flat();380		let b = other.clone().into_flat();381		a == b382	}383}384impl Eq for StrValue {}385impl PartialOrd for StrValue {386	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {387		Some(self.cmp(other))388	}389}390impl Ord for StrValue {391	fn cmp(&self, other: &Self) -> Ordering {392		let a = self.clone().into_flat();393		let b = other.clone().into_flat();394		a.cmp(&b)395	}396}397398/// Represents jsonnet number399/// Jsonnet numbers are finite f64, with NaNs disallowed400#[derive(Trace, Clone, Copy, Derivative)]401#[derivative(Debug = "transparent")]402#[repr(transparent)]403pub struct NumValue(f64);404impl NumValue {405	/// Creates a [`NumValue`], if value is finite and not NaN406	pub fn new(v: f64) -> Option<Self> {407		if !v.is_finite() {408			return None;409		}410		Some(Self(v))411	}412	pub const fn get(&self) -> f64 {413		self.0414	}415}416impl PartialEq for NumValue {417	fn eq(&self, other: &Self) -> bool {418		self.0 == other.0419	}420}421impl Eq for NumValue {}422impl Ord for NumValue {423	fn cmp(&self, other: &Self) -> Ordering {424		// Can't use `total_cmp`: its behavior for `-0` and `0`425		// is not following wanted.426		self.0.partial_cmp(&other.0).expect("NaNs are disallowed")427	}428}429impl PartialOrd for NumValue {430	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {431		Some(self.cmp(other))432	}433}434impl Display for NumValue {435	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {436		Display::fmt(&self.0, f)437	}438}439impl Deref for NumValue {440	type Target = f64;441442	fn deref(&self) -> &Self::Target {443		&self.0444	}445}446macro_rules! impl_num {447	($($ty:ty),+) => {$(448		impl From<$ty> for NumValue {449			fn from(value: $ty) -> Self {450				Self(value.into())451			}452		}453	)+};454}455impl_num!(i8, u8, i16, u16, i32, u32);456457#[derive(Clone, Copy, Debug, Error, Trace)]458pub enum ConvertNumValueError {459	#[error("overflow")]460	Overflow,461	#[error("underflow")]462	Underflow,463	#[error("non-finite")]464	NonFinite,465}466impl From<ConvertNumValueError> for Error {467	fn from(e: ConvertNumValueError) -> Self {468		Self::new(e.into())469	}470}471472macro_rules! impl_try_num {473	($($ty:ty),+) => {$(474		impl TryFrom<$ty> for NumValue {475			type Error = ConvertNumValueError;476			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {477				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};478				let value = value as f64;479				if value < MIN_SAFE_INTEGER {480					return Err(ConvertNumValueError::Underflow)481				} else if value > MAX_SAFE_INTEGER {482					return Err(ConvertNumValueError::Overflow)483				}484				// Number is finite.485				Ok(Self(value))486			}487		}488	)+};489}490impl_try_num!(usize, isize, i64, u64);491492impl TryFrom<f64> for NumValue {493	type Error = ConvertNumValueError;494495	fn try_from(value: f64) -> Result<Self, Self::Error> {496		Self::new(value).ok_or(ConvertNumValueError::NonFinite)497	}498}499impl TryFrom<f32> for NumValue {500	type Error = ConvertNumValueError;501502	fn try_from(value: f32) -> Result<Self, Self::Error> {503		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)504	}505}506507/// Represents any valid Jsonnet value.508#[derive(Debug, Clone, Trace, Default)]509pub enum Val {510	/// Represents a Jsonnet boolean.511	Bool(bool),512	/// Represents a Jsonnet null value.513	#[default]514	Null,515	/// Represents a Jsonnet string.516	Str(StrValue),517	/// Represents a Jsonnet number.518	/// Should be finite, and not NaN519	/// This restriction isn't enforced by enum, as enum field can't be marked as private520	Num(NumValue),521	/// Experimental bigint522	#[cfg(feature = "exp-bigint")]523	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),524	/// Represents a Jsonnet array.525	Arr(ArrValue),526	/// Represents a Jsonnet object.527	Obj(ObjValue),528	/// Represents a Jsonnet function.529	Func(FuncVal),530}531532#[cfg(target_pointer_width = "64")]533static_assertions::assert_eq_size!(Val, [u8; 24]);534535impl From<IndexableVal> for Val {536	fn from(v: IndexableVal) -> Self {537		match v {538			IndexableVal::Str(s) => Self::string(s),539			IndexableVal::Arr(a) => Self::Arr(a),540		}541	}542}543544impl Val {545	pub const fn as_bool(&self) -> Option<bool> {546		match self {547			Self::Bool(v) => Some(*v),548			_ => None,549		}550	}551	pub const fn as_null(&self) -> Option<()> {552		match self {553			Self::Null => Some(()),554			_ => None,555		}556	}557	pub fn as_str(&self) -> Option<IStr> {558		match self {559			Self::Str(s) => Some(s.clone().into_flat()),560			_ => None,561		}562	}563	pub const fn as_num(&self) -> Option<f64> {564		match self {565			Self::Num(n) => Some(n.get()),566			_ => None,567		}568	}569	pub fn as_arr(&self) -> Option<ArrValue> {570		match self {571			Self::Arr(a) => Some(a.clone()),572			_ => None,573		}574	}575	pub fn as_obj(&self) -> Option<ObjValue> {576		match self {577			Self::Obj(o) => Some(o.clone()),578			_ => None,579		}580	}581	pub fn as_func(&self) -> Option<FuncVal> {582		match self {583			Self::Func(f) => Some(f.clone()),584			_ => None,585		}586	}587588	pub const fn value_type(&self) -> ValType {589		match self {590			Self::Str(..) => ValType::Str,591			Self::Num(..) => ValType::Num,592			#[cfg(feature = "exp-bigint")]593			Self::BigInt(..) => ValType::BigInt,594			Self::Arr(..) => ValType::Arr,595			Self::Obj(..) => ValType::Obj,596			Self::Bool(_) => ValType::Bool,597			Self::Null => ValType::Null,598			Self::Func(..) => ValType::Func,599		}600	}601602	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {603		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {604			manifest.manifest(val.clone())605		}606		manifest_dyn(self, &format)607	}608609	pub fn to_string(&self) -> Result<IStr> {610		Ok(match self {611			Self::Bool(true) => "true".into(),612			Self::Bool(false) => "false".into(),613			Self::Null => "null".into(),614			Self::Str(s) => s.clone().into_flat(),615			_ => self.manifest(ToStringFormat).map(IStr::from)?,616		})617	}618619	pub fn into_indexable(self) -> Result<IndexableVal> {620		Ok(match self {621			Self::Str(s) => IndexableVal::Str(s.into_flat()),622			Self::Arr(arr) => IndexableVal::Arr(arr),623			_ => bail!(ValueIsNotIndexable(self.value_type())),624		})625	}626627	pub fn function(function: impl Into<FuncVal>) -> Self {628		Self::Func(function.into())629	}630	pub fn string(string: impl Into<StrValue>) -> Self {631		Self::Str(string.into())632	}633	pub fn num(num: impl Into<NumValue>) -> Self {634		Self::Num(num.into())635	}636	pub fn try_num<V, E>(num: V) -> Result<Self, E>637	where638		NumValue: TryFrom<V, Error = E>,639	{640		Ok(Self::Num(num.try_into()?))641	}642}643644impl From<IStr> for Val {645	fn from(value: IStr) -> Self {646		Self::string(value)647	}648}649impl From<String> for Val {650	fn from(value: String) -> Self {651		Self::string(value)652	}653}654impl From<&str> for Val {655	fn from(value: &str) -> Self {656		Self::string(value)657	}658}659impl From<ObjValue> for Val {660	fn from(value: ObjValue) -> Self {661		Self::Obj(value)662	}663}664665const fn is_function_like(val: &Val) -> bool {666	matches!(val, Val::Func(_))667}668669/// Native implementation of `std.primitiveEquals`670pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {671	Ok(match (val_a, val_b) {672		(Val::Bool(a), Val::Bool(b)) => a == b,673		(Val::Null, Val::Null) => true,674		(Val::Str(a), Val::Str(b)) => a == b,675		(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,676		#[cfg(feature = "exp-bigint")]677		(Val::BigInt(a), Val::BigInt(b)) => a == b,678		(Val::Arr(_), Val::Arr(_)) => {679			bail!("primitiveEquals operates on primitive types, got array")680		}681		(Val::Obj(_), Val::Obj(_)) => {682			bail!("primitiveEquals operates on primitive types, got object")683		}684		(a, b) if is_function_like(a) && is_function_like(b) => {685			bail!("cannot test equality of functions")686		}687		(_, _) => false,688	})689}690691/// Native implementation of `std.equals`692pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {693	if val_a.value_type() != val_b.value_type() {694		return Ok(false);695	}696	match (val_a, val_b) {697		(Val::Arr(a), Val::Arr(b)) => {698			if ArrValue::ptr_eq(a, b) {699				return Ok(true);700			}701			if a.len() != b.len() {702				return Ok(false);703			}704			for (a, b) in a.iter().zip(b.iter()) {705				if !equals(&a?, &b?)? {706					return Ok(false);707				}708			}709			Ok(true)710		}711		(Val::Obj(a), Val::Obj(b)) => {712			if ObjValue::ptr_eq(a, b) {713				return Ok(true);714			}715			let fields = a.fields(716				#[cfg(feature = "exp-preserve-order")]717				false,718			);719			if fields720				!= b.fields(721					#[cfg(feature = "exp-preserve-order")]722					false,723				) {724				return Ok(false);725			}726			for field in fields {727				if !equals(728					&a.get(field.clone())?.expect("field exists"),729					&b.get(field)?.expect("field exists"),730				)? {731					return Ok(false);732				}733			}734			Ok(true)735		}736		(a, b) => Ok(primitive_equals(a, b)?),737	}738}
after · crates/jrsonnet-evaluator/src/val.rs
1use std::{2	cell::RefCell,3	cmp::Ordering,4	fmt::{self, Debug, Display},5	mem::replace,6	num::NonZeroU32,7	ops::Deref,8	rc::Rc,9};1011use derivative::Derivative;12use jrsonnet_gcmodule::{Cc, Trace};13use jrsonnet_interner::IStr;14use jrsonnet_types::ValType;15use thiserror::Error;1617pub use crate::arr::{ArrValue, ArrayLike};18use crate::{19	bail,20	error::{Error, ErrorKind::*},21	function::FuncVal,22	gc::{GcHashMap, TraceBox},23	manifest::{ManifestFormat, ToStringFormat},24	tb,25	typed::BoundedUsize,26	ObjValue, Result, Unbound, WeakObjValue,27};2829pub trait ThunkValue: Trace {30	type Output;31	fn get(self: Box<Self>) -> Result<Self::Output>;32}3334#[derive(Trace)]35enum ThunkInner<T: Trace> {36	Computed(T),37	Errored(Error),38	Waiting(TraceBox<dyn ThunkValue<Output = T>>),39	Pending,40}4142/// Lazily evaluated value43#[allow(clippy::module_name_repetitions)]44#[derive(Clone, Trace)]45pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4647impl<T: Trace> Thunk<T> {48	pub fn evaluated(val: T) -> Self {49		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))50	}51	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {52		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))53	}54	pub fn errored(e: Error) -> Self {55		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))56	}57	pub fn result(res: Result<T, Error>) -> Self {58		match res {59			Ok(o) => Self::evaluated(o),60			Err(e) => Self::errored(e),61		}62	}63}6465impl<T> Thunk<T>66where67	T: Clone + Trace,68{69	pub fn force(&self) -> Result<()> {70		self.evaluate()?;71		Ok(())72	}7374	/// Evaluate thunk, or return cached value75	///76	/// # Errors77	///78	/// - Lazy value evaluation returned error79	/// - This method was called during inner value evaluation80	pub fn evaluate(&self) -> Result<T> {81		match &*self.0.borrow() {82			ThunkInner::Computed(v) => return Ok(v.clone()),83			ThunkInner::Errored(e) => return Err(e.clone()),84			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),85			ThunkInner::Waiting(..) => (),86		};87		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)88		else {89			unreachable!();90		};91		let new_value = match value.0.get() {92			Ok(v) => v,93			Err(e) => {94				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());95				return Err(e);96			}97		};98		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());99		Ok(new_value)100	}101}102103pub trait ThunkMapper<Input>: Trace {104	type Output;105	fn map(self, from: Input) -> Result<Self::Output>;106}107impl<Input> Thunk<Input>108where109	Input: Trace + Clone,110{111	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>112	where113		M: ThunkMapper<Input>,114		M::Output: Trace,115	{116		#[derive(Trace)]117		struct Mapped<Input: Trace, Mapper: Trace> {118			inner: Thunk<Input>,119			mapper: Mapper,120		}121		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>122		where123			Input: Trace + Clone,124			Mapper: ThunkMapper<Input>,125		{126			type Output = Mapper::Output;127128			fn get(self: Box<Self>) -> Result<Self::Output> {129				let value = self.inner.evaluate()?;130				let mapped = self.mapper.map(value)?;131				Ok(mapped)132			}133		}134135		Thunk::new(Mapped::<Input, M> {136			inner: self,137			mapper,138		})139	}140}141142impl<T: Trace> From<Result<T>> for Thunk<T> {143	fn from(value: Result<T>) -> Self {144		match value {145			Ok(o) => Self::evaluated(o),146			Err(e) => Self::errored(e),147		}148	}149}150impl<T, V: Trace> From<T> for Thunk<V>151where152	T: ThunkValue<Output = V>,153{154	fn from(value: T) -> Self {155		Self::new(value)156	}157}158159impl<T: Trace + Default> Default for Thunk<T> {160	fn default() -> Self {161		Self::evaluated(T::default())162	}163}164165type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);166167#[derive(Trace, Clone)]168pub struct CachedUnbound<I, T>169where170	I: Unbound<Bound = T>,171	T: Trace,172{173	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,174	value: I,175}176impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {177	pub fn new(value: I) -> Self {178		Self {179			cache: Cc::new(RefCell::new(GcHashMap::new())),180			value,181		}182	}183}184impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {185	type Bound = T;186	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {187		let cache_key = (188			sup.as_ref().map(|s| s.clone().downgrade()),189			this.as_ref().map(|t| t.clone().downgrade()),190		);191		{192			if let Some(t) = self.cache.borrow().get(&cache_key) {193				return Ok(t.clone());194			}195		}196		let bound = self.value.bind(sup, this)?;197198		{199			let mut cache = self.cache.borrow_mut();200			cache.insert(cache_key, bound.clone());201		}202203		Ok(bound)204	}205}206207impl<T: Debug + Trace> Debug for Thunk<T> {208	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {209		write!(f, "Lazy")210	}211}212impl<T: Trace> PartialEq for Thunk<T> {213	fn eq(&self, other: &Self) -> bool {214		Cc::ptr_eq(&self.0, &other.0)215	}216}217218/// Represents a Jsonnet value, which can be sliced or indexed (string or array).219#[allow(clippy::module_name_repetitions)]220pub enum IndexableVal {221	/// String.222	Str(IStr),223	/// Array.224	Arr(ArrValue),225}226impl IndexableVal {227	pub fn is_empty(&self) -> bool {228		match self {229			Self::Str(s) => s.is_empty(),230			Self::Arr(s) => s.is_empty(),231		}232	}233234	pub fn to_array(self) -> ArrValue {235		match self {236			Self::Str(s) => ArrValue::chars(s.chars()),237			Self::Arr(arr) => arr,238		}239	}240	/// Slice the value.241	///242	/// # Implementation243	///244	/// For strings, will create a copy of specified interval.245	///246	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.247	pub fn slice(248		self,249		index: Option<i32>,250		end: Option<i32>,251		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,252	) -> Result<Self> {253		match &self {254			Self::Str(s) => {255				let mut computed_len = None;256				let mut get_len = || {257					computed_len.map_or_else(258						|| {259							let len = s.chars().count();260							let _ = computed_len.insert(len);261							len262						},263						|len| len,264					)265				};266				let mut get_idx = |pos: Option<i32>, default| {267					match pos {268						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),269						// No need to clamp, as iterator interface is used270						Some(v) => v as usize,271						None => default,272					}273				};274275				let index = get_idx(index, 0);276				let end = get_idx(end, usize::MAX);277				let step = step.as_deref().copied().unwrap_or(1);278279				if index >= end {280					return Ok(Self::Str("".into()));281				}282283				Ok(Self::Str(284					(s.chars()285						.skip(index)286						.take(end - index)287						.step_by(step)288						.collect::<String>())289					.into(),290				))291			}292			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(293				index,294				end,295				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),296			))),297		}298	}299}300301#[derive(Debug, Clone, Trace)]302pub enum StrValue {303	Flat(IStr),304	Tree(Rc<(StrValue, StrValue, usize)>),305}306impl StrValue {307	pub fn concat(a: Self, b: Self) -> Self {308		// TODO: benchmark for an optimal value, currently just a arbitrary choice309		const STRING_EXTEND_THRESHOLD: usize = 100;310311		if a.is_empty() {312			b313		} else if b.is_empty() {314			a315		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {316			Self::Flat(format!("{a}{b}").into())317		} else {318			let len = a.len() + b.len();319			Self::Tree(Rc::new((a, b, len)))320		}321	}322	pub fn into_flat(self) -> IStr {323		#[cold]324		fn write_buf(s: &StrValue, out: &mut String) {325			match s {326				StrValue::Flat(f) => out.push_str(f),327				StrValue::Tree(t) => {328					write_buf(&t.0, out);329					write_buf(&t.1, out);330				}331			}332		}333		match self {334			Self::Flat(f) => f,335			Self::Tree(_) => {336				let mut buf = String::with_capacity(self.len());337				write_buf(&self, &mut buf);338				buf.into()339			}340		}341	}342	pub fn len(&self) -> usize {343		match self {344			Self::Flat(v) => v.len(),345			Self::Tree(t) => t.2,346		}347	}348	pub fn is_empty(&self) -> bool {349		match self {350			Self::Flat(v) => v.is_empty(),351			// Can't create non-flat empty string352			Self::Tree(_) => false,353		}354	}355}356impl<T> From<T> for StrValue357where358	IStr: From<T>,359{360	fn from(value: T) -> Self {361		Self::Flat(IStr::from(value))362	}363}364impl Display for StrValue {365	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {366		match self {367			Self::Flat(v) => write!(f, "{v}"),368			Self::Tree(t) => {369				write!(f, "{}", t.0)?;370				write!(f, "{}", t.1)371			}372		}373	}374}375impl PartialEq for StrValue {376	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.377	#[allow(clippy::unconditional_recursion)]378	fn eq(&self, other: &Self) -> bool {379		let a = self.clone().into_flat();380		let b = other.clone().into_flat();381		a == b382	}383}384impl Eq for StrValue {}385impl PartialOrd for StrValue {386	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {387		Some(self.cmp(other))388	}389}390impl Ord for StrValue {391	fn cmp(&self, other: &Self) -> Ordering {392		let a = self.clone().into_flat();393		let b = other.clone().into_flat();394		a.cmp(&b)395	}396}397398/// Represents jsonnet number399/// Jsonnet numbers are finite f64, with NaNs disallowed400#[derive(Trace, Clone, Copy, Derivative)]401#[derivative(Debug = "transparent")]402#[repr(transparent)]403pub struct NumValue(f64);404impl NumValue {405	/// Creates a [`NumValue`], if value is finite and not NaN406	pub fn new(v: f64) -> Option<Self> {407		if !v.is_finite() {408			return None;409		}410		Some(Self(v))411	}412	#[inline]413	pub const fn get(&self) -> f64 {414		self.0415	}416}417impl PartialEq for NumValue {418	fn eq(&self, other: &Self) -> bool {419		self.0 == other.0420	}421}422impl Eq for NumValue {}423impl Ord for NumValue {424	#[inline]425	fn cmp(&self, other: &Self) -> Ordering {426		// Can't use `total_cmp`: its behavior for `-0` and `0`427		// is not following wanted.428		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }429	}430}431impl PartialOrd for NumValue {432	#[inline]433	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {434		Some(self.cmp(other))435	}436}437impl Display for NumValue {438	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {439		Display::fmt(&self.0, f)440	}441}442impl Deref for NumValue {443	type Target = f64;444445	#[inline]446	fn deref(&self) -> &Self::Target {447		&self.0448	}449}450macro_rules! impl_num {451	($($ty:ty),+) => {$(452		impl From<$ty> for NumValue {453			#[inline]454			fn from(value: $ty) -> Self {455				Self(value.into())456			}457		}458	)+};459}460impl_num!(i8, u8, i16, u16, i32, u32);461462#[derive(Clone, Copy, Debug, Error, Trace)]463pub enum ConvertNumValueError {464	#[error("overflow")]465	Overflow,466	#[error("underflow")]467	Underflow,468	#[error("non-finite")]469	NonFinite,470}471impl From<ConvertNumValueError> for Error {472	fn from(e: ConvertNumValueError) -> Self {473		Self::new(e.into())474	}475}476477macro_rules! impl_try_num {478	($($ty:ty),+) => {$(479		impl TryFrom<$ty> for NumValue {480			type Error = ConvertNumValueError;481			#[inline]482			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {483				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};484				let value = value as f64;485				if value < MIN_SAFE_INTEGER {486					return Err(ConvertNumValueError::Underflow)487				} else if value > MAX_SAFE_INTEGER {488					return Err(ConvertNumValueError::Overflow)489				}490				// Number is finite.491				Ok(Self(value))492			}493		}494	)+};495}496impl_try_num!(usize, isize, i64, u64);497498impl TryFrom<f64> for NumValue {499	type Error = ConvertNumValueError;500501	#[inline]502	fn try_from(value: f64) -> Result<Self, Self::Error> {503		Self::new(value).ok_or(ConvertNumValueError::NonFinite)504	}505}506impl TryFrom<f32> for NumValue {507	type Error = ConvertNumValueError;508509	#[inline]510	fn try_from(value: f32) -> Result<Self, Self::Error> {511		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)512	}513}514515/// Represents any valid Jsonnet value.516#[derive(Debug, Clone, Trace, Default)]517pub enum Val {518	/// Represents a Jsonnet boolean.519	Bool(bool),520	/// Represents a Jsonnet null value.521	#[default]522	Null,523	/// Represents a Jsonnet string.524	Str(StrValue),525	/// Represents a Jsonnet number.526	/// Should be finite, and not NaN527	/// This restriction isn't enforced by enum, as enum field can't be marked as private528	Num(NumValue),529	/// Experimental bigint530	#[cfg(feature = "exp-bigint")]531	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),532	/// Represents a Jsonnet array.533	Arr(ArrValue),534	/// Represents a Jsonnet object.535	Obj(ObjValue),536	/// Represents a Jsonnet function.537	Func(FuncVal),538}539540#[cfg(target_pointer_width = "64")]541static_assertions::assert_eq_size!(Val, [u8; 24]);542543impl From<IndexableVal> for Val {544	fn from(v: IndexableVal) -> Self {545		match v {546			IndexableVal::Str(s) => Self::string(s),547			IndexableVal::Arr(a) => Self::Arr(a),548		}549	}550}551552impl Val {553	pub const fn as_bool(&self) -> Option<bool> {554		match self {555			Self::Bool(v) => Some(*v),556			_ => None,557		}558	}559	pub const fn as_null(&self) -> Option<()> {560		match self {561			Self::Null => Some(()),562			_ => None,563		}564	}565	pub fn as_str(&self) -> Option<IStr> {566		match self {567			Self::Str(s) => Some(s.clone().into_flat()),568			_ => None,569		}570	}571	pub const fn as_num(&self) -> Option<f64> {572		match self {573			Self::Num(n) => Some(n.get()),574			_ => None,575		}576	}577	pub fn as_arr(&self) -> Option<ArrValue> {578		match self {579			Self::Arr(a) => Some(a.clone()),580			_ => None,581		}582	}583	pub fn as_obj(&self) -> Option<ObjValue> {584		match self {585			Self::Obj(o) => Some(o.clone()),586			_ => None,587		}588	}589	pub fn as_func(&self) -> Option<FuncVal> {590		match self {591			Self::Func(f) => Some(f.clone()),592			_ => None,593		}594	}595596	pub const fn value_type(&self) -> ValType {597		match self {598			Self::Str(..) => ValType::Str,599			Self::Num(..) => ValType::Num,600			#[cfg(feature = "exp-bigint")]601			Self::BigInt(..) => ValType::BigInt,602			Self::Arr(..) => ValType::Arr,603			Self::Obj(..) => ValType::Obj,604			Self::Bool(_) => ValType::Bool,605			Self::Null => ValType::Null,606			Self::Func(..) => ValType::Func,607		}608	}609610	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {611		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {612			manifest.manifest(val.clone())613		}614		manifest_dyn(self, &format)615	}616617	pub fn to_string(&self) -> Result<IStr> {618		Ok(match self {619			Self::Bool(true) => "true".into(),620			Self::Bool(false) => "false".into(),621			Self::Null => "null".into(),622			Self::Str(s) => s.clone().into_flat(),623			_ => self.manifest(ToStringFormat).map(IStr::from)?,624		})625	}626627	pub fn into_indexable(self) -> Result<IndexableVal> {628		Ok(match self {629			Self::Str(s) => IndexableVal::Str(s.into_flat()),630			Self::Arr(arr) => IndexableVal::Arr(arr),631			_ => bail!(ValueIsNotIndexable(self.value_type())),632		})633	}634635	pub fn function(function: impl Into<FuncVal>) -> Self {636		Self::Func(function.into())637	}638	pub fn string(string: impl Into<StrValue>) -> Self {639		Self::Str(string.into())640	}641	pub fn num(num: impl Into<NumValue>) -> Self {642		Self::Num(num.into())643	}644	pub fn try_num<V, E>(num: V) -> Result<Self, E>645	where646		NumValue: TryFrom<V, Error = E>,647	{648		Ok(Self::Num(num.try_into()?))649	}650}651652impl From<IStr> for Val {653	fn from(value: IStr) -> Self {654		Self::string(value)655	}656}657impl From<String> for Val {658	fn from(value: String) -> Self {659		Self::string(value)660	}661}662impl From<&str> for Val {663	fn from(value: &str) -> Self {664		Self::string(value)665	}666}667impl From<ObjValue> for Val {668	fn from(value: ObjValue) -> Self {669		Self::Obj(value)670	}671}672673const fn is_function_like(val: &Val) -> bool {674	matches!(val, Val::Func(_))675}676677/// Native implementation of `std.primitiveEquals`678pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {679	Ok(match (val_a, val_b) {680		(Val::Bool(a), Val::Bool(b)) => a == b,681		(Val::Null, Val::Null) => true,682		(Val::Str(a), Val::Str(b)) => a == b,683		(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,684		#[cfg(feature = "exp-bigint")]685		(Val::BigInt(a), Val::BigInt(b)) => a == b,686		(Val::Arr(_), Val::Arr(_)) => {687			bail!("primitiveEquals operates on primitive types, got array")688		}689		(Val::Obj(_), Val::Obj(_)) => {690			bail!("primitiveEquals operates on primitive types, got object")691		}692		(a, b) if is_function_like(a) && is_function_like(b) => {693			bail!("cannot test equality of functions")694		}695		(_, _) => false,696	})697}698699/// Native implementation of `std.equals`700pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {701	if val_a.value_type() != val_b.value_type() {702		return Ok(false);703	}704	match (val_a, val_b) {705		(Val::Arr(a), Val::Arr(b)) => {706			if ArrValue::ptr_eq(a, b) {707				return Ok(true);708			}709			if a.len() != b.len() {710				return Ok(false);711			}712			for (a, b) in a.iter().zip(b.iter()) {713				if !equals(&a?, &b?)? {714					return Ok(false);715				}716			}717			Ok(true)718		}719		(Val::Obj(a), Val::Obj(b)) => {720			if ObjValue::ptr_eq(a, b) {721				return Ok(true);722			}723			let fields = a.fields(724				#[cfg(feature = "exp-preserve-order")]725				false,726			);727			if fields728				!= b.fields(729					#[cfg(feature = "exp-preserve-order")]730					false,731				) {732				return Ok(false);733			}734			for field in fields {735				if !equals(736					&a.get(field.clone())?.expect("field exists"),737					&b.get(field)?.expect("field exists"),738				)? {739					return Ok(false);740				}741			}742			Ok(true)743		}744		(a, b) => Ok(primitive_equals(a, b)?),745	}746}
modifiednix/jrsonnet.nixdiffbeforeafterboth
--- a/nix/jrsonnet.nix
+++ b/nix/jrsonnet.nix
@@ -17,7 +17,7 @@
     pname = "jrsonnet";
     version = "current${optionalString withNightlyFeatures "-nightly"}${optionalString withExperimentalFeatures "-experimental"}";
 
-    cargoExtraArgs = "--locked --features=mimalloc,legacy-this-file${optionalString withNightlyFeatures ",nightly"}${optionalString withExperimentalFeatures ",experimental"}";
+    cargoExtraArgs = "--locked --features=mimalloc${optionalString withNightlyFeatures ",nightly"}${optionalString withExperimentalFeatures ",experimental"}";
 
     nativeBuildInputs = [makeWrapper];