git.delta.rocks / jrsonnet / refs/commits / 6baa18de6571

difftreelog

refactor drop derivative dependency

Yaroslav Bolyukin2024-11-24parent: #a9a2382.patch.diff
in: master

5 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -194,7 +194,7 @@
  "heck",
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -244,17 +244,6 @@
 dependencies = [
  "generic-array",
  "typenum",
-]
-
-[[package]]
-name = "derivative"
-version = "2.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn 1.0.109",
 ]
 
 [[package]]
@@ -483,7 +472,6 @@
 dependencies = [
  "annotate-snippets",
  "anyhow",
- "derivative",
  "hashbrown 0.14.5",
  "hi-doc",
  "jrsonnet-gcmodule",
@@ -533,7 +521,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -551,7 +539,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
  "syn-dissect-closure",
 ]
 
@@ -694,7 +682,7 @@
  "proc-macro2",
  "quote",
  "regex-syntax",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1019,7 +1007,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1123,17 +1111,6 @@
 
 [[package]]
 name = "syn"
-version = "1.0.109"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-ident",
-]
-
-[[package]]
-name = "syn"
 version = "2.0.76"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525"
@@ -1151,7 +1128,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1202,7 +1179,7 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
 
 [[package]]
@@ -1428,5 +1405,5 @@
 dependencies = [
  "proc-macro2",
  "quote",
- "syn 2.0.76",
+ "syn",
 ]
modifiedCargo.tomldiffbeforeafterboth
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -71,7 +71,6 @@
 static_assertions = "1.1"
 rustc-hash = "1.1"
 num-bigint = "0.4.5"
-derivative = "2.2.0"
 strsim = "0.11.0"
 proc-macro2 = "1.0"
 quote = "1.0"
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -59,5 +59,4 @@
 hi-doc = { workspace = true, optional = true }
 # Bigint
 num-bigint = { workspace = true, features = ["serde"], optional = true }
-derivative.workspace = true
 stacker = "0.1.15"
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,8 +1,7 @@
 use std::mem::replace;
 
-use jrsonnet_gcmodule::Trace;
 use jrsonnet_interner::IStr;
-use jrsonnet_parser::{LocExpr, ParamsDesc};
+use jrsonnet_parser::ParamsDesc;
 
 use super::{arglike::ArgsLike, builtin::BuiltinParam};
 use crate::{
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;14pub use jrsonnet_macros::Thunk;15use jrsonnet_types::ValType;16use thiserror::Error;1718pub use crate::arr::{ArrValue, ArrayLike};19use crate::{20	bail,21	error::{Error, ErrorKind::*},22	function::FuncVal,23	gc::{GcHashMap, TraceBox},24	manifest::{ManifestFormat, ToStringFormat},25	tb,26	typed::BoundedUsize,27	ObjValue, Result, Unbound, WeakObjValue,28};2930pub trait ThunkValue: Trace {31	type Output;32	fn get(self: Box<Self>) -> Result<Self::Output>;33}3435#[derive(Trace)]36pub struct ThunkValueClosure<D: Trace, O: 'static> {37	env: D,38	// Carries no data, as it is not a real closure, all the39	// captured environment is stored in `env` field.40	#[trace(skip)]41	closure: fn(D) -> Result<O>,42}43impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {44	pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {45		Self { env, closure }46	}47}48impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {49	type Output = O;5051	fn get(self: Box<Self>) -> Result<Self::Output> {52		(self.closure)(self.env)53	}54}5556#[derive(Trace)]57enum ThunkInner<T: Trace> {58	Computed(T),59	Errored(Error),60	Waiting(TraceBox<dyn ThunkValue<Output = T>>),61	Pending,62}6364/// Lazily evaluated value65#[allow(clippy::module_name_repetitions)]66#[derive(Clone, Trace)]67pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);6869impl<T: Trace> Thunk<T> {70	pub fn evaluated(val: T) -> Self {71		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))72	}73	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {74		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))75	}76	pub fn errored(e: Error) -> Self {77		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))78	}79	pub fn result(res: Result<T, Error>) -> Self {80		match res {81			Ok(o) => Self::evaluated(o),82			Err(e) => Self::errored(e),83		}84	}85}8687impl<T> Thunk<T>88where89	T: Clone + Trace,90{91	pub fn force(&self) -> Result<()> {92		self.evaluate()?;93		Ok(())94	}9596	/// Evaluate thunk, or return cached value97	///98	/// # Errors99	///100	/// - Lazy value evaluation returned error101	/// - This method was called during inner value evaluation102	pub fn evaluate(&self) -> Result<T> {103		match &*self.0.borrow() {104			ThunkInner::Computed(v) => return Ok(v.clone()),105			ThunkInner::Errored(e) => return Err(e.clone()),106			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),107			ThunkInner::Waiting(..) => (),108		};109		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)110		else {111			unreachable!();112		};113		let new_value = match value.0.get() {114			Ok(v) => v,115			Err(e) => {116				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());117				return Err(e);118			}119		};120		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());121		Ok(new_value)122	}123}124125pub trait ThunkMapper<Input>: Trace {126	type Output;127	fn map(self, from: Input) -> Result<Self::Output>;128}129impl<Input> Thunk<Input>130where131	Input: Trace + Clone,132{133	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>134	where135		M: ThunkMapper<Input>,136		M::Output: Trace,137	{138		let inner = self;139		Thunk!(move || {140			let value = inner.evaluate()?;141			let mapped = mapper.map(value)?;142			Ok(mapped)143		})144	}145}146147impl<T: Trace> From<Result<T>> for Thunk<T> {148	fn from(value: Result<T>) -> Self {149		match value {150			Ok(o) => Self::evaluated(o),151			Err(e) => Self::errored(e),152		}153	}154}155impl<T, V: Trace> From<T> for Thunk<V>156where157	T: ThunkValue<Output = V>,158{159	fn from(value: T) -> Self {160		Self::new(value)161	}162}163164impl<T: Trace + Default> Default for Thunk<T> {165	fn default() -> Self {166		Self::evaluated(T::default())167	}168}169170type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);171172#[derive(Trace, Clone)]173pub struct CachedUnbound<I, T>174where175	I: Unbound<Bound = T>,176	T: Trace,177{178	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,179	value: I,180}181impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {182	pub fn new(value: I) -> Self {183		Self {184			cache: Cc::new(RefCell::new(GcHashMap::new())),185			value,186		}187	}188}189impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {190	type Bound = T;191	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {192		let cache_key = (193			sup.as_ref().map(|s| s.clone().downgrade()),194			this.as_ref().map(|t| t.clone().downgrade()),195		);196		{197			if let Some(t) = self.cache.borrow().get(&cache_key) {198				return Ok(t.clone());199			}200		}201		let bound = self.value.bind(sup, this)?;202203		{204			let mut cache = self.cache.borrow_mut();205			cache.insert(cache_key, bound.clone());206		}207208		Ok(bound)209	}210}211212impl<T: Debug + Trace> Debug for Thunk<T> {213	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {214		write!(f, "Lazy")215	}216}217impl<T: Trace> PartialEq for Thunk<T> {218	fn eq(&self, other: &Self) -> bool {219		Cc::ptr_eq(&self.0, &other.0)220	}221}222223/// Represents a Jsonnet value, which can be sliced or indexed (string or array).224#[allow(clippy::module_name_repetitions)]225pub enum IndexableVal {226	/// String.227	Str(IStr),228	/// Array.229	Arr(ArrValue),230}231impl IndexableVal {232	pub fn is_empty(&self) -> bool {233		match self {234			Self::Str(s) => s.is_empty(),235			Self::Arr(s) => s.is_empty(),236		}237	}238239	pub fn to_array(self) -> ArrValue {240		match self {241			Self::Str(s) => ArrValue::chars(s.chars()),242			Self::Arr(arr) => arr,243		}244	}245	/// Slice the value.246	///247	/// # Implementation248	///249	/// For strings, will create a copy of specified interval.250	///251	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.252	pub fn slice(253		self,254		index: Option<i32>,255		end: Option<i32>,256		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,257	) -> Result<Self> {258		match &self {259			Self::Str(s) => {260				let mut computed_len = None;261				let mut get_len = || {262					computed_len.map_or_else(263						|| {264							let len = s.chars().count();265							let _ = computed_len.insert(len);266							len267						},268						|len| len,269					)270				};271				let mut get_idx = |pos: Option<i32>, default| {272					match pos {273						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),274						// No need to clamp, as iterator interface is used275						Some(v) => v as usize,276						None => default,277					}278				};279280				let index = get_idx(index, 0);281				let end = get_idx(end, usize::MAX);282				let step = step.as_deref().copied().unwrap_or(1);283284				if index >= end {285					return Ok(Self::Str("".into()));286				}287288				Ok(Self::Str(289					(s.chars()290						.skip(index)291						.take(end - index)292						.step_by(step)293						.collect::<String>())294					.into(),295				))296			}297			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(298				index,299				end,300				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),301			))),302		}303	}304}305306#[derive(Debug, Clone, Trace)]307pub enum StrValue {308	Flat(IStr),309	Tree(Rc<(StrValue, StrValue, usize)>),310}311impl StrValue {312	pub fn concat(a: Self, b: Self) -> Self {313		// TODO: benchmark for an optimal value, currently just a arbitrary choice314		const STRING_EXTEND_THRESHOLD: usize = 100;315316		if a.is_empty() {317			b318		} else if b.is_empty() {319			a320		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {321			Self::Flat(format!("{a}{b}").into())322		} else {323			let len = a.len() + b.len();324			Self::Tree(Rc::new((a, b, len)))325		}326	}327	pub fn into_flat(self) -> IStr {328		#[cold]329		fn write_buf(s: &StrValue, out: &mut String) {330			match s {331				StrValue::Flat(f) => out.push_str(f),332				StrValue::Tree(t) => {333					write_buf(&t.0, out);334					write_buf(&t.1, out);335				}336			}337		}338		match self {339			Self::Flat(f) => f,340			Self::Tree(_) => {341				let mut buf = String::with_capacity(self.len());342				write_buf(&self, &mut buf);343				buf.into()344			}345		}346	}347	pub fn len(&self) -> usize {348		match self {349			Self::Flat(v) => v.len(),350			Self::Tree(t) => t.2,351		}352	}353	pub fn is_empty(&self) -> bool {354		match self {355			Self::Flat(v) => v.is_empty(),356			// Can't create non-flat empty string357			Self::Tree(_) => false,358		}359	}360}361impl<T> From<T> for StrValue362where363	IStr: From<T>,364{365	fn from(value: T) -> Self {366		Self::Flat(IStr::from(value))367	}368}369impl Display for StrValue {370	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {371		match self {372			Self::Flat(v) => write!(f, "{v}"),373			Self::Tree(t) => {374				write!(f, "{}", t.0)?;375				write!(f, "{}", t.1)376			}377		}378	}379}380impl PartialEq for StrValue {381	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.382	#[allow(clippy::unconditional_recursion)]383	fn eq(&self, other: &Self) -> bool {384		let a = self.clone().into_flat();385		let b = other.clone().into_flat();386		a == b387	}388}389impl Eq for StrValue {}390impl PartialOrd for StrValue {391	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {392		Some(self.cmp(other))393	}394}395impl Ord for StrValue {396	fn cmp(&self, other: &Self) -> Ordering {397		let a = self.clone().into_flat();398		let b = other.clone().into_flat();399		a.cmp(&b)400	}401}402403/// Represents jsonnet number404/// Jsonnet numbers are finite f64, with NaNs disallowed405#[derive(Trace, Clone, Copy, Derivative)]406#[derivative(Debug = "transparent")]407#[repr(transparent)]408pub struct NumValue(f64);409impl NumValue {410	/// Creates a [`NumValue`], if value is finite and not NaN411	pub fn new(v: f64) -> Option<Self> {412		if !v.is_finite() {413			return None;414		}415		Some(Self(v))416	}417	#[inline]418	pub const fn get(&self) -> f64 {419		self.0420	}421}422impl PartialEq for NumValue {423	fn eq(&self, other: &Self) -> bool {424		self.0 == other.0425	}426}427impl Eq for NumValue {}428impl Ord for NumValue {429	#[inline]430	fn cmp(&self, other: &Self) -> Ordering {431		// Can't use `total_cmp`: its behavior for `-0` and `0`432		// is not following wanted.433		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }434	}435}436impl PartialOrd for NumValue {437	#[inline]438	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {439		Some(self.cmp(other))440	}441}442impl Display for NumValue {443	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {444		Display::fmt(&self.0, f)445	}446}447impl Deref for NumValue {448	type Target = f64;449450	#[inline]451	fn deref(&self) -> &Self::Target {452		&self.0453	}454}455macro_rules! impl_num {456	($($ty:ty),+) => {$(457		impl From<$ty> for NumValue {458			#[inline]459			fn from(value: $ty) -> Self {460				Self(value.into())461			}462		}463	)+};464}465impl_num!(i8, u8, i16, u16, i32, u32);466467#[derive(Clone, Copy, Debug, Error, Trace)]468pub enum ConvertNumValueError {469	#[error("overflow")]470	Overflow,471	#[error("underflow")]472	Underflow,473	#[error("non-finite")]474	NonFinite,475}476impl From<ConvertNumValueError> for Error {477	fn from(e: ConvertNumValueError) -> Self {478		Self::new(e.into())479	}480}481482macro_rules! impl_try_num {483	($($ty:ty),+) => {$(484		impl TryFrom<$ty> for NumValue {485			type Error = ConvertNumValueError;486			#[inline]487			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {488				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};489				let value = value as f64;490				if value < MIN_SAFE_INTEGER {491					return Err(ConvertNumValueError::Underflow)492				} else if value > MAX_SAFE_INTEGER {493					return Err(ConvertNumValueError::Overflow)494				}495				// Number is finite.496				Ok(Self(value))497			}498		}499	)+};500}501impl_try_num!(usize, isize, i64, u64);502503impl TryFrom<f64> for NumValue {504	type Error = ConvertNumValueError;505506	#[inline]507	fn try_from(value: f64) -> Result<Self, Self::Error> {508		Self::new(value).ok_or(ConvertNumValueError::NonFinite)509	}510}511impl TryFrom<f32> for NumValue {512	type Error = ConvertNumValueError;513514	#[inline]515	fn try_from(value: f32) -> Result<Self, Self::Error> {516		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)517	}518}519520/// Represents any valid Jsonnet value.521#[derive(Debug, Clone, Trace, Default)]522pub enum Val {523	/// Represents a Jsonnet boolean.524	Bool(bool),525	/// Represents a Jsonnet null value.526	#[default]527	Null,528	/// Represents a Jsonnet string.529	Str(StrValue),530	/// Represents a Jsonnet number.531	/// Should be finite, and not NaN532	/// This restriction isn't enforced by enum, as enum field can't be marked as private533	Num(NumValue),534	/// Experimental bigint535	#[cfg(feature = "exp-bigint")]536	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),537	/// Represents a Jsonnet array.538	Arr(ArrValue),539	/// Represents a Jsonnet object.540	Obj(ObjValue),541	/// Represents a Jsonnet function.542	Func(FuncVal),543}544545#[cfg(target_pointer_width = "64")]546static_assertions::assert_eq_size!(Val, [u8; 24]);547548impl From<IndexableVal> for Val {549	fn from(v: IndexableVal) -> Self {550		match v {551			IndexableVal::Str(s) => Self::string(s),552			IndexableVal::Arr(a) => Self::Arr(a),553		}554	}555}556557impl Val {558	pub const fn as_bool(&self) -> Option<bool> {559		match self {560			Self::Bool(v) => Some(*v),561			_ => None,562		}563	}564	pub const fn as_null(&self) -> Option<()> {565		match self {566			Self::Null => Some(()),567			_ => None,568		}569	}570	pub fn as_str(&self) -> Option<IStr> {571		match self {572			Self::Str(s) => Some(s.clone().into_flat()),573			_ => None,574		}575	}576	pub const fn as_num(&self) -> Option<f64> {577		match self {578			Self::Num(n) => Some(n.get()),579			_ => None,580		}581	}582	pub fn as_arr(&self) -> Option<ArrValue> {583		match self {584			Self::Arr(a) => Some(a.clone()),585			_ => None,586		}587	}588	pub fn as_obj(&self) -> Option<ObjValue> {589		match self {590			Self::Obj(o) => Some(o.clone()),591			_ => None,592		}593	}594	pub fn as_func(&self) -> Option<FuncVal> {595		match self {596			Self::Func(f) => Some(f.clone()),597			_ => None,598		}599	}600601	pub const fn value_type(&self) -> ValType {602		match self {603			Self::Str(..) => ValType::Str,604			Self::Num(..) => ValType::Num,605			#[cfg(feature = "exp-bigint")]606			Self::BigInt(..) => ValType::BigInt,607			Self::Arr(..) => ValType::Arr,608			Self::Obj(..) => ValType::Obj,609			Self::Bool(_) => ValType::Bool,610			Self::Null => ValType::Null,611			Self::Func(..) => ValType::Func,612		}613	}614615	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {616		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {617			manifest.manifest(val.clone())618		}619		manifest_dyn(self, &format)620	}621622	pub fn to_string(&self) -> Result<IStr> {623		Ok(match self {624			Self::Bool(true) => "true".into(),625			Self::Bool(false) => "false".into(),626			Self::Null => "null".into(),627			Self::Str(s) => s.clone().into_flat(),628			_ => self.manifest(ToStringFormat).map(IStr::from)?,629		})630	}631632	pub fn into_indexable(self) -> Result<IndexableVal> {633		Ok(match self {634			Self::Str(s) => IndexableVal::Str(s.into_flat()),635			Self::Arr(arr) => IndexableVal::Arr(arr),636			_ => bail!(ValueIsNotIndexable(self.value_type())),637		})638	}639640	pub fn function(function: impl Into<FuncVal>) -> Self {641		Self::Func(function.into())642	}643	pub fn string(string: impl Into<StrValue>) -> Self {644		Self::Str(string.into())645	}646	pub fn num(num: impl Into<NumValue>) -> Self {647		Self::Num(num.into())648	}649	pub fn try_num<V, E>(num: V) -> Result<Self, E>650	where651		NumValue: TryFrom<V, Error = E>,652	{653		Ok(Self::Num(num.try_into()?))654	}655}656657impl From<IStr> for Val {658	fn from(value: IStr) -> Self {659		Self::string(value)660	}661}662impl From<String> for Val {663	fn from(value: String) -> Self {664		Self::string(value)665	}666}667impl From<&str> for Val {668	fn from(value: &str) -> Self {669		Self::string(value)670	}671}672impl From<ObjValue> for Val {673	fn from(value: ObjValue) -> Self {674		Self::Obj(value)675	}676}677678const fn is_function_like(val: &Val) -> bool {679	matches!(val, Val::Func(_))680}681682/// Native implementation of `std.primitiveEquals`683pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {684	Ok(match (val_a, val_b) {685		(Val::Bool(a), Val::Bool(b)) => a == b,686		(Val::Null, Val::Null) => true,687		(Val::Str(a), Val::Str(b)) => a == b,688		(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,689		#[cfg(feature = "exp-bigint")]690		(Val::BigInt(a), Val::BigInt(b)) => a == b,691		(Val::Arr(_), Val::Arr(_)) => {692			bail!("primitiveEquals operates on primitive types, got array")693		}694		(Val::Obj(_), Val::Obj(_)) => {695			bail!("primitiveEquals operates on primitive types, got object")696		}697		(a, b) if is_function_like(a) && is_function_like(b) => {698			bail!("cannot test equality of functions")699		}700		(_, _) => false,701	})702}703704/// Native implementation of `std.equals`705pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {706	if val_a.value_type() != val_b.value_type() {707		return Ok(false);708	}709	match (val_a, val_b) {710		(Val::Arr(a), Val::Arr(b)) => {711			if ArrValue::ptr_eq(a, b) {712				return Ok(true);713			}714			if a.len() != b.len() {715				return Ok(false);716			}717			for (a, b) in a.iter().zip(b.iter()) {718				if !equals(&a?, &b?)? {719					return Ok(false);720				}721			}722			Ok(true)723		}724		(Val::Obj(a), Val::Obj(b)) => {725			if ObjValue::ptr_eq(a, b) {726				return Ok(true);727			}728			let fields = a.fields(729				#[cfg(feature = "exp-preserve-order")]730				false,731			);732			if fields733				!= b.fields(734					#[cfg(feature = "exp-preserve-order")]735					false,736				) {737				return Ok(false);738			}739			for field in fields {740				if !equals(741					&a.get(field.clone())?.expect("field exists"),742					&b.get(field)?.expect("field exists"),743				)? {744					return Ok(false);745				}746			}747			Ok(true)748		}749		(a, b) => Ok(primitive_equals(a, b)?),750	}751}
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 jrsonnet_gcmodule::{Cc, Trace};12use jrsonnet_interner::IStr;13pub use jrsonnet_macros::Thunk;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)]35pub struct ThunkValueClosure<D: Trace, O: 'static> {36	env: D,37	// Carries no data, as it is not a real closure, all the38	// captured environment is stored in `env` field.39	#[trace(skip)]40	closure: fn(D) -> Result<O>,41}42impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {43	pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {44		Self { env, closure }45	}46}47impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {48	type Output = O;4950	fn get(self: Box<Self>) -> Result<Self::Output> {51		(self.closure)(self.env)52	}53}5455#[derive(Trace)]56enum ThunkInner<T: Trace> {57	Computed(T),58	Errored(Error),59	Waiting(TraceBox<dyn ThunkValue<Output = T>>),60	Pending,61}6263/// Lazily evaluated value64#[allow(clippy::module_name_repetitions)]65#[derive(Clone, Trace)]66pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);6768impl<T: Trace> Thunk<T> {69	pub fn evaluated(val: T) -> Self {70		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))71	}72	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {73		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))74	}75	pub fn errored(e: Error) -> Self {76		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))77	}78	pub fn result(res: Result<T, Error>) -> Self {79		match res {80			Ok(o) => Self::evaluated(o),81			Err(e) => Self::errored(e),82		}83	}84}8586impl<T> Thunk<T>87where88	T: Clone + Trace,89{90	pub fn force(&self) -> Result<()> {91		self.evaluate()?;92		Ok(())93	}9495	/// Evaluate thunk, or return cached value96	///97	/// # Errors98	///99	/// - Lazy value evaluation returned error100	/// - This method was called during inner value evaluation101	pub fn evaluate(&self) -> Result<T> {102		match &*self.0.borrow() {103			ThunkInner::Computed(v) => return Ok(v.clone()),104			ThunkInner::Errored(e) => return Err(e.clone()),105			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),106			ThunkInner::Waiting(..) => (),107		};108		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)109		else {110			unreachable!();111		};112		let new_value = match value.0.get() {113			Ok(v) => v,114			Err(e) => {115				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());116				return Err(e);117			}118		};119		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());120		Ok(new_value)121	}122}123124pub trait ThunkMapper<Input>: Trace {125	type Output;126	fn map(self, from: Input) -> Result<Self::Output>;127}128impl<Input> Thunk<Input>129where130	Input: Trace + Clone,131{132	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>133	where134		M: ThunkMapper<Input>,135		M::Output: Trace,136	{137		let inner = self;138		Thunk!(move || {139			let value = inner.evaluate()?;140			let mapped = mapper.map(value)?;141			Ok(mapped)142		})143	}144}145146impl<T: Trace> From<Result<T>> for Thunk<T> {147	fn from(value: Result<T>) -> Self {148		match value {149			Ok(o) => Self::evaluated(o),150			Err(e) => Self::errored(e),151		}152	}153}154impl<T, V: Trace> From<T> for Thunk<V>155where156	T: ThunkValue<Output = V>,157{158	fn from(value: T) -> Self {159		Self::new(value)160	}161}162163impl<T: Trace + Default> Default for Thunk<T> {164	fn default() -> Self {165		Self::evaluated(T::default())166	}167}168169type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);170171#[derive(Trace, Clone)]172pub struct CachedUnbound<I, T>173where174	I: Unbound<Bound = T>,175	T: Trace,176{177	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,178	value: I,179}180impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {181	pub fn new(value: I) -> Self {182		Self {183			cache: Cc::new(RefCell::new(GcHashMap::new())),184			value,185		}186	}187}188impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {189	type Bound = T;190	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {191		let cache_key = (192			sup.as_ref().map(|s| s.clone().downgrade()),193			this.as_ref().map(|t| t.clone().downgrade()),194		);195		{196			if let Some(t) = self.cache.borrow().get(&cache_key) {197				return Ok(t.clone());198			}199		}200		let bound = self.value.bind(sup, this)?;201202		{203			let mut cache = self.cache.borrow_mut();204			cache.insert(cache_key, bound.clone());205		}206207		Ok(bound)208	}209}210211impl<T: Debug + Trace> Debug for Thunk<T> {212	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {213		write!(f, "Lazy")214	}215}216impl<T: Trace> PartialEq for Thunk<T> {217	fn eq(&self, other: &Self) -> bool {218		Cc::ptr_eq(&self.0, &other.0)219	}220}221222/// Represents a Jsonnet value, which can be sliced or indexed (string or array).223#[allow(clippy::module_name_repetitions)]224pub enum IndexableVal {225	/// String.226	Str(IStr),227	/// Array.228	Arr(ArrValue),229}230impl IndexableVal {231	pub fn is_empty(&self) -> bool {232		match self {233			Self::Str(s) => s.is_empty(),234			Self::Arr(s) => s.is_empty(),235		}236	}237238	pub fn to_array(self) -> ArrValue {239		match self {240			Self::Str(s) => ArrValue::chars(s.chars()),241			Self::Arr(arr) => arr,242		}243	}244	/// Slice the value.245	///246	/// # Implementation247	///248	/// For strings, will create a copy of specified interval.249	///250	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.251	pub fn slice(252		self,253		index: Option<i32>,254		end: Option<i32>,255		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,256	) -> Result<Self> {257		match &self {258			Self::Str(s) => {259				let mut computed_len = None;260				let mut get_len = || {261					computed_len.map_or_else(262						|| {263							let len = s.chars().count();264							let _ = computed_len.insert(len);265							len266						},267						|len| len,268					)269				};270				let mut get_idx = |pos: Option<i32>, default| {271					match pos {272						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),273						// No need to clamp, as iterator interface is used274						Some(v) => v as usize,275						None => default,276					}277				};278279				let index = get_idx(index, 0);280				let end = get_idx(end, usize::MAX);281				let step = step.as_deref().copied().unwrap_or(1);282283				if index >= end {284					return Ok(Self::Str("".into()));285				}286287				Ok(Self::Str(288					(s.chars()289						.skip(index)290						.take(end - index)291						.step_by(step)292						.collect::<String>())293					.into(),294				))295			}296			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(297				index,298				end,299				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),300			))),301		}302	}303}304305#[derive(Debug, Clone, Trace)]306pub enum StrValue {307	Flat(IStr),308	Tree(Rc<(StrValue, StrValue, usize)>),309}310impl StrValue {311	pub fn concat(a: Self, b: Self) -> Self {312		// TODO: benchmark for an optimal value, currently just a arbitrary choice313		const STRING_EXTEND_THRESHOLD: usize = 100;314315		if a.is_empty() {316			b317		} else if b.is_empty() {318			a319		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {320			Self::Flat(format!("{a}{b}").into())321		} else {322			let len = a.len() + b.len();323			Self::Tree(Rc::new((a, b, len)))324		}325	}326	pub fn into_flat(self) -> IStr {327		#[cold]328		fn write_buf(s: &StrValue, out: &mut String) {329			match s {330				StrValue::Flat(f) => out.push_str(f),331				StrValue::Tree(t) => {332					write_buf(&t.0, out);333					write_buf(&t.1, out);334				}335			}336		}337		match self {338			Self::Flat(f) => f,339			Self::Tree(_) => {340				let mut buf = String::with_capacity(self.len());341				write_buf(&self, &mut buf);342				buf.into()343			}344		}345	}346	pub fn len(&self) -> usize {347		match self {348			Self::Flat(v) => v.len(),349			Self::Tree(t) => t.2,350		}351	}352	pub fn is_empty(&self) -> bool {353		match self {354			Self::Flat(v) => v.is_empty(),355			// Can't create non-flat empty string356			Self::Tree(_) => false,357		}358	}359}360impl<T> From<T> for StrValue361where362	IStr: From<T>,363{364	fn from(value: T) -> Self {365		Self::Flat(IStr::from(value))366	}367}368impl Display for StrValue {369	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {370		match self {371			Self::Flat(v) => write!(f, "{v}"),372			Self::Tree(t) => {373				write!(f, "{}", t.0)?;374				write!(f, "{}", t.1)375			}376		}377	}378}379impl PartialEq for StrValue {380	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.381	#[allow(clippy::unconditional_recursion)]382	fn eq(&self, other: &Self) -> bool {383		let a = self.clone().into_flat();384		let b = other.clone().into_flat();385		a == b386	}387}388impl Eq for StrValue {}389impl PartialOrd for StrValue {390	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {391		Some(self.cmp(other))392	}393}394impl Ord for StrValue {395	fn cmp(&self, other: &Self) -> Ordering {396		let a = self.clone().into_flat();397		let b = other.clone().into_flat();398		a.cmp(&b)399	}400}401402/// Represents jsonnet number403/// Jsonnet numbers are finite f64, with NaNs disallowed404#[derive(Trace, Clone, Copy)]405#[repr(transparent)]406pub struct NumValue(f64);407impl NumValue {408	/// Creates a [`NumValue`], if value is finite and not NaN409	pub fn new(v: f64) -> Option<Self> {410		if !v.is_finite() {411			return None;412		}413		Some(Self(v))414	}415	#[inline]416	pub const fn get(&self) -> f64 {417		self.0418	}419}420impl PartialEq for NumValue {421	fn eq(&self, other: &Self) -> bool {422		self.0 == other.0423	}424}425impl Eq for NumValue {}426impl Ord for NumValue {427	#[inline]428	fn cmp(&self, other: &Self) -> Ordering {429		// Can't use `total_cmp`: its behavior for `-0` and `0`430		// is not following wanted.431		unsafe { self.0.partial_cmp(&other.0).unwrap_unchecked() }432	}433}434impl PartialOrd for NumValue {435	#[inline]436	fn partial_cmp(&self, other: &Self) -> Option<Ordering> {437		Some(self.cmp(other))438	}439}440impl Debug for NumValue {441	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {442		Debug::fmt(&self.0, f)443	}444}445impl Display for NumValue {446	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {447		Display::fmt(&self.0, f)448	}449}450impl Deref for NumValue {451	type Target = f64;452453	#[inline]454	fn deref(&self) -> &Self::Target {455		&self.0456	}457}458macro_rules! impl_num {459	($($ty:ty),+) => {$(460		impl From<$ty> for NumValue {461			#[inline]462			fn from(value: $ty) -> Self {463				Self(value.into())464			}465		}466	)+};467}468impl_num!(i8, u8, i16, u16, i32, u32);469470#[derive(Clone, Copy, Debug, Error, Trace)]471pub enum ConvertNumValueError {472	#[error("overflow")]473	Overflow,474	#[error("underflow")]475	Underflow,476	#[error("non-finite")]477	NonFinite,478}479impl From<ConvertNumValueError> for Error {480	fn from(e: ConvertNumValueError) -> Self {481		Self::new(e.into())482	}483}484485macro_rules! impl_try_num {486	($($ty:ty),+) => {$(487		impl TryFrom<$ty> for NumValue {488			type Error = ConvertNumValueError;489			#[inline]490			fn try_from(value: $ty) -> Result<Self, ConvertNumValueError> {491				use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER};492				let value = value as f64;493				if value < MIN_SAFE_INTEGER {494					return Err(ConvertNumValueError::Underflow)495				} else if value > MAX_SAFE_INTEGER {496					return Err(ConvertNumValueError::Overflow)497				}498				// Number is finite.499				Ok(Self(value))500			}501		}502	)+};503}504impl_try_num!(usize, isize, i64, u64);505506impl TryFrom<f64> for NumValue {507	type Error = ConvertNumValueError;508509	#[inline]510	fn try_from(value: f64) -> Result<Self, Self::Error> {511		Self::new(value).ok_or(ConvertNumValueError::NonFinite)512	}513}514impl TryFrom<f32> for NumValue {515	type Error = ConvertNumValueError;516517	#[inline]518	fn try_from(value: f32) -> Result<Self, Self::Error> {519		Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite)520	}521}522523/// Represents any valid Jsonnet value.524#[derive(Debug, Clone, Trace, Default)]525pub enum Val {526	/// Represents a Jsonnet boolean.527	Bool(bool),528	/// Represents a Jsonnet null value.529	#[default]530	Null,531	/// Represents a Jsonnet string.532	Str(StrValue),533	/// Represents a Jsonnet number.534	/// Should be finite, and not NaN535	/// This restriction isn't enforced by enum, as enum field can't be marked as private536	Num(NumValue),537	/// Experimental bigint538	#[cfg(feature = "exp-bigint")]539	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),540	/// Represents a Jsonnet array.541	Arr(ArrValue),542	/// Represents a Jsonnet object.543	Obj(ObjValue),544	/// Represents a Jsonnet function.545	Func(FuncVal),546}547548#[cfg(target_pointer_width = "64")]549static_assertions::assert_eq_size!(Val, [u8; 24]);550551impl From<IndexableVal> for Val {552	fn from(v: IndexableVal) -> Self {553		match v {554			IndexableVal::Str(s) => Self::string(s),555			IndexableVal::Arr(a) => Self::Arr(a),556		}557	}558}559560impl Val {561	pub const fn as_bool(&self) -> Option<bool> {562		match self {563			Self::Bool(v) => Some(*v),564			_ => None,565		}566	}567	pub const fn as_null(&self) -> Option<()> {568		match self {569			Self::Null => Some(()),570			_ => None,571		}572	}573	pub fn as_str(&self) -> Option<IStr> {574		match self {575			Self::Str(s) => Some(s.clone().into_flat()),576			_ => None,577		}578	}579	pub const fn as_num(&self) -> Option<f64> {580		match self {581			Self::Num(n) => Some(n.get()),582			_ => None,583		}584	}585	pub fn as_arr(&self) -> Option<ArrValue> {586		match self {587			Self::Arr(a) => Some(a.clone()),588			_ => None,589		}590	}591	pub fn as_obj(&self) -> Option<ObjValue> {592		match self {593			Self::Obj(o) => Some(o.clone()),594			_ => None,595		}596	}597	pub fn as_func(&self) -> Option<FuncVal> {598		match self {599			Self::Func(f) => Some(f.clone()),600			_ => None,601		}602	}603604	pub const fn value_type(&self) -> ValType {605		match self {606			Self::Str(..) => ValType::Str,607			Self::Num(..) => ValType::Num,608			#[cfg(feature = "exp-bigint")]609			Self::BigInt(..) => ValType::BigInt,610			Self::Arr(..) => ValType::Arr,611			Self::Obj(..) => ValType::Obj,612			Self::Bool(_) => ValType::Bool,613			Self::Null => ValType::Null,614			Self::Func(..) => ValType::Func,615		}616	}617618	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {619		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {620			manifest.manifest(val.clone())621		}622		manifest_dyn(self, &format)623	}624625	pub fn to_string(&self) -> Result<IStr> {626		Ok(match self {627			Self::Bool(true) => "true".into(),628			Self::Bool(false) => "false".into(),629			Self::Null => "null".into(),630			Self::Str(s) => s.clone().into_flat(),631			_ => self.manifest(ToStringFormat).map(IStr::from)?,632		})633	}634635	pub fn into_indexable(self) -> Result<IndexableVal> {636		Ok(match self {637			Self::Str(s) => IndexableVal::Str(s.into_flat()),638			Self::Arr(arr) => IndexableVal::Arr(arr),639			_ => bail!(ValueIsNotIndexable(self.value_type())),640		})641	}642643	pub fn function(function: impl Into<FuncVal>) -> Self {644		Self::Func(function.into())645	}646	pub fn string(string: impl Into<StrValue>) -> Self {647		Self::Str(string.into())648	}649	pub fn num(num: impl Into<NumValue>) -> Self {650		Self::Num(num.into())651	}652	pub fn try_num<V, E>(num: V) -> Result<Self, E>653	where654		NumValue: TryFrom<V, Error = E>,655	{656		Ok(Self::Num(num.try_into()?))657	}658}659660impl From<IStr> for Val {661	fn from(value: IStr) -> Self {662		Self::string(value)663	}664}665impl From<String> for Val {666	fn from(value: String) -> Self {667		Self::string(value)668	}669}670impl From<&str> for Val {671	fn from(value: &str) -> Self {672		Self::string(value)673	}674}675impl From<ObjValue> for Val {676	fn from(value: ObjValue) -> Self {677		Self::Obj(value)678	}679}680681const fn is_function_like(val: &Val) -> bool {682	matches!(val, Val::Func(_))683}684685/// Native implementation of `std.primitiveEquals`686pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {687	Ok(match (val_a, val_b) {688		(Val::Bool(a), Val::Bool(b)) => a == b,689		(Val::Null, Val::Null) => true,690		(Val::Str(a), Val::Str(b)) => a == b,691		(Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,692		#[cfg(feature = "exp-bigint")]693		(Val::BigInt(a), Val::BigInt(b)) => a == b,694		(Val::Arr(_), Val::Arr(_)) => {695			bail!("primitiveEquals operates on primitive types, got array")696		}697		(Val::Obj(_), Val::Obj(_)) => {698			bail!("primitiveEquals operates on primitive types, got object")699		}700		(a, b) if is_function_like(a) && is_function_like(b) => {701			bail!("cannot test equality of functions")702		}703		(_, _) => false,704	})705}706707/// Native implementation of `std.equals`708pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {709	if val_a.value_type() != val_b.value_type() {710		return Ok(false);711	}712	match (val_a, val_b) {713		(Val::Arr(a), Val::Arr(b)) => {714			if ArrValue::ptr_eq(a, b) {715				return Ok(true);716			}717			if a.len() != b.len() {718				return Ok(false);719			}720			for (a, b) in a.iter().zip(b.iter()) {721				if !equals(&a?, &b?)? {722					return Ok(false);723				}724			}725			Ok(true)726		}727		(Val::Obj(a), Val::Obj(b)) => {728			if ObjValue::ptr_eq(a, b) {729				return Ok(true);730			}731			let fields = a.fields(732				#[cfg(feature = "exp-preserve-order")]733				false,734			);735			if fields736				!= b.fields(737					#[cfg(feature = "exp-preserve-order")]738					false,739				) {740				return Ok(false);741			}742			for field in fields {743				if !equals(744					&a.get(field.clone())?.expect("field exists"),745					&b.get(field)?.expect("field exists"),746				)? {747					return Ok(false);748				}749			}750			Ok(true)751		}752		(a, b) => Ok(primitive_equals(a, b)?),753	}754}