git.delta.rocks / jrsonnet / refs/commits / 0e2224254bcf

difftreelog

fix enforce Val::Num finityness at type level

Yaroslav Bolyukin2024-05-19parent: #88907d8.patch.diff
in: master

13 files changed

modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -120,7 +120,7 @@
 	}
 
 	fn get_cheap(&self, index: usize) -> Option<Val> {
-		self.0.get(index).map(|v| Val::Num(f64::from(*v)))
+		self.0.get(index).map(|v| Val::Num((*v).into()))
 	}
 	fn is_cheap(&self) -> bool {
 		true
@@ -399,7 +399,7 @@
 	}
 
 	fn get_cheap(&self, index: usize) -> Option<Val> {
-		self.range().nth(index).map(|i| Val::Num(f64::from(i)))
+		self.range().nth(index).map(|i| Val::Num(i.into()))
 	}
 	fn is_cheap(&self) -> bool {
 		true
@@ -430,12 +430,12 @@
 }
 
 #[derive(Trace, Debug, Clone)]
-pub struct MappedArray<const WithIndex: bool> {
+pub struct MappedArray<const WITH_INDEX: bool> {
 	inner: ArrValue,
 	cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,
 	mapper: FuncVal,
 }
-impl<const WithIndex: bool> MappedArray<WithIndex> {
+impl<const WITH_INDEX: bool> MappedArray<WITH_INDEX> {
 	pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {
 		let len = inner.len();
 		Self {
@@ -445,14 +445,14 @@
 		}
 	}
 	fn evaluate(&self, index: usize, value: Val) -> Result<Val> {
-		if WithIndex {
+		if WITH_INDEX {
 			self.mapper.evaluate_simple(&(index, value), false)
 		} else {
 			self.mapper.evaluate_simple(&(value,), false)
 		}
 	}
 }
-impl<const WithIndex: bool> ArrayLike for MappedArray<WithIndex> {
+impl<const WITH_INDEX: bool> ArrayLike for MappedArray<WITH_INDEX> {
 	fn len(&self) -> usize {
 		self.cached.borrow().len()
 	}
@@ -493,12 +493,12 @@
 	}
 	fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
 		#[derive(Trace)]
-		struct ArrayElement<const WithIndex: bool> {
-			arr_thunk: MappedArray<WithIndex>,
+		struct ArrayElement<const WITH_INDEX: bool> {
+			arr_thunk: MappedArray<WITH_INDEX>,
 			index: usize,
 		}
 
-		impl<const WithIndex: bool> ThunkValue for ArrayElement<WithIndex> {
+		impl<const WITH_INDEX: bool> ThunkValue for ArrayElement<WITH_INDEX> {
 			type Output = Val;
 
 			fn get(self: Box<Self>) -> Result<Self::Output> {
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,7 +1,5 @@
 use std::{
-	cmp::Ordering,
-	fmt::{Debug, Display},
-	path::PathBuf,
+	cmp::Ordering, convert::Infallible, fmt::{Debug, Display}, path::PathBuf
 };
 
 use jrsonnet_gcmodule::Trace;
@@ -14,6 +12,7 @@
 	function::{builtin::ParamDefault, CallLocation},
 	stdlib::format::FormatError,
 	typed::TypeLocError,
+	val::ConvertNumValueError,
 	ObjValue,
 };
 
@@ -236,6 +235,9 @@
 	#[error("invalid unicode codepoint: {0}")]
 	InvalidUnicodeCodepointGot(u32),
 
+	#[error("convert num value: {0}")]
+	ConvertNumValue(#[from] ConvertNumValueError),
+
 	#[error("format error: {0}")]
 	Format(#[from] FormatError),
 	#[error("type error: {0}")]
@@ -259,6 +261,12 @@
 	}
 }
 
+impl From<Infallible> for Error {
+	fn from(_value: Infallible) -> Self {
+		unreachable!()
+	}
+}
+
 /// Single stack trace frame
 #[derive(Clone, Debug, Trace)]
 pub struct StackTraceElement {
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -17,7 +17,7 @@
 	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
 	function::{CallLocation, FuncDesc, FuncVal},
 	typed::Typed,
-	val::{CachedUnbound, IndexableVal, StrValue, Thunk, ThunkValue},
+	val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk, ThunkValue},
 	Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
 	ResultExt, State, Unbound, Val,
 };
@@ -37,7 +37,7 @@
 	}
 	Some(match &*expr.0 {
 		Expr::Str(s) => Val::string(s.clone()),
-		Expr::Num(n) => Val::Num(*n),
+		Expr::Num(n) => Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values")),
 		Expr::Literal(LiteralType::False) => Val::Bool(false),
 		Expr::Literal(LiteralType::True) => Val::Bool(true),
 		Expr::Literal(LiteralType::Null) => Val::Null,
@@ -438,7 +438,7 @@
 		Literal(LiteralType::Null) => Val::Null,
 		Parened(e) => evaluate(ctx, e)?,
 		Str(v) => Val::string(v.clone()),
-		Num(v) => Val::new_checked_num(*v)?,
+		Num(v) => Val::try_num(*v)?,
 		// I have tried to remove special behavior from super by implementing standalone-super
 		// expresion, but looks like this case still needs special treatment.
 		//
@@ -530,6 +530,7 @@
 						n.value_type(),
 					)),
 					(Val::Arr(v), Val::Num(n)) => {
+						let n = n.get();
 						if n.fract() > f64::EPSILON {
 							bail!(FractionalIndex)
 						}
@@ -553,13 +554,13 @@
 							.clone()
 							.into_flat()
 							.chars()
-							.skip(n as usize)
+							.skip(n.get() as usize)
 							.take(1)
 							.collect::<String>()
 							.into();
 						if v.is_empty() {
 							let size = s.into_flat().chars().count();
-							bail!(StringBoundsError(n as usize, size))
+							bail!(StringBoundsError(n.get() as usize, size))
 						}
 						StrValue::Flat(v)
 					}),
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -17,10 +17,10 @@
 	use UnaryOpType::*;
 	use Val::*;
 	Ok(match (op, b) {
-		(Plus, Num(n)) => Num(*n),
-		(Minus, Num(n)) => Num(-*n),
+		(Plus, Num(n)) => Val::Num(*n),
+		(Minus, Num(n)) => Val::try_num(-n.get())?,
 		(Not, Bool(v)) => Bool(!v),
-		(BitNot, Num(n)) => Num(!(*n as i64) as f64),
+		(BitNot, Num(n)) => Val::try_num(!(n.get() as i64) as f64)?,
 		(op, o) => bail!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())),
 	})
 }
@@ -40,7 +40,7 @@
 		(Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),
 		(Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),
 
-		(Num(v1), Num(v2)) => Val::new_checked_num(v1 + v2)?,
+		(Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,
 		#[cfg(feature = "exp-bigint")]
 		(BigInt(a), BigInt(b)) => BigInt(Box::new(&**a + &**b)),
 		_ => bail!(BinaryOperatorDoesNotOperateOnValues(
@@ -55,10 +55,10 @@
 	use Val::*;
 	match (a, b) {
 		(Num(a), Num(b)) => {
-			if *b == 0.0 {
+			if b.get() == 0.0 {
 				bail!(DivisionByZero)
 			}
-			Ok(Num(a % b))
+			Ok(Val::try_num(a.get() % b.get())?)
 		}
 		(Str(str), vals) => {
 			String::into_untyped(std_format(&str.clone().into_flat(), vals.clone())?)
@@ -143,39 +143,39 @@
 		(Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone().into_flat(), true)),
 		(a, Mod, b) => evaluate_mod_op(a, b)?,
 
-		(Str(v1), Mul, Num(v2)) => Val::string(v1.to_string().repeat(*v2 as usize)),
+		(Str(v1), Mul, Num(v2)) => Val::string(v1.to_string().repeat(v2.get() as usize)),
 
 		// Bool X Bool
 		(Bool(a), And, Bool(b)) => Bool(*a && *b),
 		(Bool(a), Or, Bool(b)) => Bool(*a || *b),
 
 		// Num X Num
-		(Num(v1), Mul, Num(v2)) => Val::new_checked_num(v1 * v2)?,
+		(Num(v1), Mul, Num(v2)) => Val::try_num(v1.get() * v2.get())?,
 		(Num(v1), Div, Num(v2)) => {
-			if *v2 == 0.0 {
+			if v2.get() == 0.0 {
 				bail!(DivisionByZero)
 			}
-			Val::new_checked_num(v1 / v2)?
+			Val::try_num(v1.get() / v2.get())?
 		}
 
-		(Num(v1), Sub, Num(v2)) => Val::new_checked_num(v1 - v2)?,
+		(Num(v1), Sub, Num(v2)) => Val::try_num(v1.get() - v2.get())?,
 
-		(Num(v1), BitAnd, Num(v2)) => Num((*v1 as i64 & *v2 as i64) as f64),
-		(Num(v1), BitOr, Num(v2)) => Num((*v1 as i64 | *v2 as i64) as f64),
-		(Num(v1), BitXor, Num(v2)) => Num((*v1 as i64 ^ *v2 as i64) as f64),
+		(Num(v1), BitAnd, Num(v2)) => Val::try_num((v1.get() as i64 & v2.get() as i64) as f64)?,
+		(Num(v1), BitOr, Num(v2)) => Val::try_num((v1.get() as i64 | v2.get() as i64) as f64)?,
+		(Num(v1), BitXor, Num(v2)) => Val::try_num((v1.get() as i64 ^ v2.get() as i64) as f64)?,
 		(Num(v1), Lhs, Num(v2)) => {
-			if *v2 < 0.0 {
+			if v2.get() < 0.0 {
 				bail!("shift by negative exponent")
 			}
-			let exp = ((*v2 as i64) & 63) as u32;
-			Num((*v1 as i64).wrapping_shl(exp) as f64)
+			let exp = ((v2.get() as i64) & 63) as u32;
+			Val::try_num((v1.get() as i64).wrapping_shl(exp) as f64)?
 		}
 		(Num(v1), Rhs, Num(v2)) => {
-			if *v2 < 0.0 {
+			if v2.get() < 0.0 {
 				bail!("shift by negative exponent")
 			}
-			let exp = ((*v2 as i64) & 63) as u32;
-			Num((*v1 as i64).wrapping_shr(exp) as f64)
+			let exp = ((v2.get() as i64) & 63) as u32;
+			Val::try_num((v1.get() as i64).wrapping_shr(exp) as f64)?
 		}
 
 		// Bigint X Bigint
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -2,7 +2,7 @@
 
 use jrsonnet_interner::IStr;
 use serde::{
-	de::Visitor,
+	de::{self, Visitor},
 	ser::{
 		Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
 		SerializeTupleStruct, SerializeTupleVariant,
@@ -11,7 +11,8 @@
 };
 
 use crate::{
-	arr::ArrValue, runtime_error, Error as JrError, ObjValue, ObjValueBuilder, Result, State, Val,
+	arr::ArrValue, runtime_error, val::NumValue, Error as JrError, ObjValue, ObjValueBuilder,
+	Result, State, Val,
 };
 
 impl<'de> Deserialize<'de> for Val {
@@ -37,22 +38,21 @@
 
 			fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
 				Ok(Val::Bool(v))
 			}
 			fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
-				if !v.is_finite() {
-					return Err(E::custom("only finite numbers are supported"));
-				}
-				Ok(Val::Num(v))
+				Ok(Val::Num(NumValue::new(v).ok_or_else(|| {
+					E::custom("only finite numbers are supported")
+				})?))
 			}
 			fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
 				Ok(Val::string(v))
 			}
@@ -67,27 +67,27 @@
 			// }
 			fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
-				Ok(Val::Num(v as f64))
+				Ok(Val::Num(NumValue::new(v as f64).expect("no overflow")))
 			}
 			fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
-				Ok(Val::Num(v as f64))
+				Ok(Val::Num(NumValue::new(v as f64).expect("no overflow")))
 			}
 
 			fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
 				Ok(Val::Arr(ArrValue::bytes(v.into())))
 			}
 
 			fn visit_none<E>(self) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
 				Ok(Val::Null)
 			}
@@ -100,7 +100,7 @@
 
 			fn visit_unit<E>(self) -> Result<Self::Value, E>
 			where
-				E: serde::de::Error,
+				E: de::Error,
 			{
 				Ok(Val::Null)
 			}
@@ -114,7 +114,7 @@
 
 			fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
 			where
-				A: serde::de::SeqAccess<'de>,
+				A: de::SeqAccess<'de>,
 			{
 				let mut out = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);
 
@@ -127,7 +127,7 @@
 
 			fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
 			where
-				A: serde::de::MapAccess<'de>,
+				A: de::MapAccess<'de>,
 			{
 				let mut out = map
 					.size_hint()
@@ -159,11 +159,12 @@
 			Self::Null => serializer.serialize_none(),
 			Self::Str(s) => serializer.serialize_str(&s.clone().into_flat()),
 			Self::Num(n) => {
+				let n = n.get();
 				if n.fract() == 0.0 {
-					let n = *n as i64;
+					let n = n as i64;
 					serializer.serialize_i64(n)
 				} else {
-					serializer.serialize_f64(*n)
+					serializer.serialize_f64(n)
 				}
 			}
 			#[cfg(feature = "exp-bigint")]
@@ -449,15 +450,15 @@
 	}
 
 	fn serialize_i8(self, v: i8) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::Num(v.into()))
 	}
 
 	fn serialize_i16(self, v: i16) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::Num(v.into()))
 	}
 
 	fn serialize_i32(self, v: i32) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::Num(v.into()))
 	}
 
 	fn serialize_i64(self, v: i64) -> Result<Val> {
@@ -465,15 +466,15 @@
 	}
 
 	fn serialize_u8(self, v: u8) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::Num(v.into()))
 	}
 
 	fn serialize_u16(self, v: u16) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::Num(v.into()))
 	}
 
 	fn serialize_u32(self, v: u32) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::Num(v.into()))
 	}
 
 	fn serialize_u64(self, v: u64) -> Result<Val> {
@@ -481,11 +482,11 @@
 	}
 
 	fn serialize_f32(self, v: f32) -> Result<Val> {
-		Ok(Val::Num(f64::from(v)))
+		Ok(Val::try_num(f64::from(v))?)
 	}
 
 	fn serialize_f64(self, v: f64) -> Result<Val> {
-		Ok(Val::Num(v))
+		Ok(Val::try_num(v)?)
 	}
 
 	fn serialize_char(self, v: char) -> Result<Val> {
modifiedcrates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/stdlib/format.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs
@@ -604,10 +604,13 @@
 			}
 		}
 		ConvTypeV::Char => match value.clone() {
-			Val::Num(n) => tmp_out.push(
-				std::char::from_u32(n as u32)
-					.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,
-			),
+			Val::Num(n) => {
+				let n = n.get();
+				tmp_out.push(
+					std::char::from_u32(n as u32)
+						.ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,
+				)
+			}
 			Val::Str(s) => {
 				let s = s.into_flat();
 				if s.chars().count() != 1 {
@@ -786,6 +789,7 @@
 #[cfg(test)]
 pub mod test_format {
 	use super::*;
+	use crate::val::NumValue;
 
 	#[test]
 	fn parse() {
@@ -799,17 +803,21 @@
 		);
 	}
 
+	fn num(v: f64) -> Val {
+		Val::Num(NumValue::new(v).expect("finite"))
+	}
+
 	#[test]
 	fn octals() {
-		assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");
-		assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");
-		assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), "  10");
-		assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");
-		assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");
-		assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");
-		assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10  ");
-		assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");
-		assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");
+		assert_eq!(format_arr("%#o", &[num(8.0)]).unwrap(), "010");
+		assert_eq!(format_arr("%#4o", &[num(8.0)]).unwrap(), " 010");
+		assert_eq!(format_arr("%4o", &[num(8.0)]).unwrap(), "  10");
+		assert_eq!(format_arr("%04o", &[num(8.0)]).unwrap(), "0010");
+		assert_eq!(format_arr("%+4o", &[num(8.0)]).unwrap(), " +10");
+		assert_eq!(format_arr("%+04o", &[num(8.0)]).unwrap(), "+010");
+		assert_eq!(format_arr("%-4o", &[num(8.0)]).unwrap(), "10  ");
+		assert_eq!(format_arr("%+-4o", &[num(8.0)]).unwrap(), "+10 ");
+		assert_eq!(format_arr("%+-04o", &[num(8.0)]).unwrap(), "+10 ");
 	}
 
 	#[test]
@@ -817,7 +825,7 @@
 		assert_eq!(
 			format_arr(
 				"How much error budget is left looking at our %.3f%% availability gurantees?",
-				&[Val::Num(4.0)]
+				&[num(4.0)]
 			)
 			.unwrap(),
 			"How much error budget is left looking at our 4.000% availability gurantees?"
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -10,7 +10,7 @@
 	bail,
 	function::{native::NativeDesc, FuncDesc, FuncVal},
 	typed::CheckType,
-	val::{IndexableVal, StrValue, ThunkMapper},
+	val::{IndexableVal, NumValue, StrValue, ThunkMapper},
 	ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,
 };
 
@@ -120,7 +120,8 @@
 	}
 }
 
-const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;
+pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;
+pub const MIN_SAFE_INTEGER: f64 = -MAX_SAFE_INTEGER;
 
 macro_rules! impl_int {
 	($($ty:ty)*) => {$(
@@ -131,6 +132,7 @@
 				<Self as Typed>::TYPE.check(&value)?;
 				match value {
 					Val::Num(n) => {
+						let n = n.get();
 						#[allow(clippy::float_cmp)]
 						if n.trunc() != n {
 							bail!(
@@ -143,9 +145,8 @@
 					_ => unreachable!(),
 				}
 			}
-			#[allow(clippy::cast_lossless)]
 			fn into_untyped(value: Self) -> Result<Val> {
-				Ok(Val::Num(value as f64))
+				Ok(Val::Num(value.into()))
 			}
 		}
 	)*};
@@ -187,6 +188,7 @@
 				<Self as Typed>::TYPE.check(&value)?;
 				match value {
 					Val::Num(n) => {
+						let n = n.get();
 						#[allow(clippy::float_cmp)]
 						if n.trunc() != n {
 							bail!(
@@ -202,7 +204,7 @@
 
 			#[allow(clippy::cast_lossless)]
 			fn into_untyped(value: Self) -> Result<Val> {
-				Ok(Val::Num(value.0 as f64))
+				Ok(Val::try_num(value.0)?)
 			}
 		}
 	)*};
@@ -220,13 +222,13 @@
 	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);
 
 	fn into_untyped(value: Self) -> Result<Val> {
-		Ok(Val::Num(value))
+		Ok(Val::try_num(value)?)
 	}
 
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
-			Val::Num(n) => Ok(n),
+			Val::Num(n) => Ok(n.get()),
 			_ => unreachable!(),
 		}
 	}
@@ -237,13 +239,13 @@
 	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);
 
 	fn into_untyped(value: Self) -> Result<Val> {
-		Ok(Val::Num(value.0))
+		Ok(Val::try_num(value.0)?)
 	}
 
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
-			Val::Num(n) => Ok(Self(n)),
+			Val::Num(n) => Ok(Self(n.get())),
 			_ => unreachable!(),
 		}
 	}
@@ -253,16 +255,14 @@
 		&ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));
 
 	fn into_untyped(value: Self) -> Result<Val> {
-		if value > MAX_SAFE_INTEGER as Self {
-			bail!("number is too large")
-		}
-		Ok(Val::Num(value as f64))
+		Ok(Val::try_num(value)?)
 	}
 
 	fn from_untyped(value: Val) -> Result<Self> {
 		<Self as Typed>::TYPE.check(&value)?;
 		match value {
 			Val::Num(n) => {
+				let n = n.get();
 				#[allow(clippy::float_cmp)]
 				if n.trunc() != n {
 					bail!("cannot convert number with fractional part to usize")
@@ -479,7 +479,7 @@
 	const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));
 
 	fn into_untyped(_: Self) -> Result<Val> {
-		Ok(Val::Num(-1.0))
+		Ok(Val::Num(NumValue::new(-1.0).expect("finite")))
 	}
 
 	fn from_untyped(value: Val) -> Result<Self> {
@@ -679,3 +679,19 @@
 		))
 	}
 }
+
+impl Typed for NumValue {
+	const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);
+
+	fn into_untyped(typed: Self) -> Result<Val> {
+		Ok(Val::Num(typed))
+	}
+
+	fn from_untyped(untyped: Val) -> Result<Self> {
+		Self::TYPE.check(&untyped)?;
+		match untyped {
+			Val::Num(v) => Ok(v),
+			_ => unreachable!(),
+		}
+	}
+}
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -1,6 +1,6 @@
 use std::{fmt::Display, rc::Rc};
 
-mod conversions;
+pub(crate) mod conversions;
 pub use conversions::*;
 use jrsonnet_gcmodule::Trace;
 pub use jrsonnet_types::{ComplexValType, ValType};
@@ -155,10 +155,11 @@
 			},
 			Self::BoundedNumber(from, to) => {
 				if let Val::Num(n) = value {
-					if from.map(|from| from > *n).unwrap_or(false)
-						|| to.map(|to| to < *n).unwrap_or(false)
+					let n = n.get();
+					if from.map(|from| from > n).unwrap_or(false)
+						|| to.map(|to| to < n).unwrap_or(false)
 					{
-						return Err(TypeError::BoundsFailed(*n, *from, *to).into());
+						return Err(TypeError::BoundsFailed(n, *from, *to).into());
 					}
 					Ok(())
 				} else {
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	num::NonZeroU32,6	rc::Rc,7};89use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::IStr;11use jrsonnet_types::ValType;1213pub use crate::arr::{ArrValue, ArrayLike};14use crate::{15	bail,16	error::{Error, ErrorKind::*},17	function::FuncVal,18	gc::{GcHashMap, TraceBox},19	manifest::{ManifestFormat, ToStringFormat},20	tb,21	typed::BoundedUsize,22	ObjValue, Result, Unbound, WeakObjValue,23};2425pub trait ThunkValue: Trace {26	type Output;27	fn get(self: Box<Self>) -> Result<Self::Output>;28}2930#[derive(Trace)]31enum ThunkInner<T: Trace> {32	Computed(T),33	Errored(Error),34	Waiting(TraceBox<dyn ThunkValue<Output = T>>),35	Pending,36}3738/// Lazily evaluated value39#[allow(clippy::module_name_repetitions)]40#[derive(Clone, Trace)]41pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4243impl<T: Trace> Thunk<T> {44	pub fn evaluated(val: T) -> Self {45		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))46	}47	pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {48		Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))49	}50	pub fn errored(e: Error) -> Self {51		Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))52	}53	pub fn result(res: Result<T, Error>) -> Self {54		match res {55			Ok(o) => Self::evaluated(o),56			Err(e) => Self::errored(e),57		}58	}59}6061impl<T> Thunk<T>62where63	T: Clone + Trace,64{65	pub fn force(&self) -> Result<()> {66		self.evaluate()?;67		Ok(())68	}6970	/// Evaluate thunk, or return cached value71	///72	/// # Errors73	///74	/// - Lazy value evaluation returned error75	/// - This method was called during inner value evaluation76	pub fn evaluate(&self) -> Result<T> {77		match &*self.0.borrow() {78			ThunkInner::Computed(v) => return Ok(v.clone()),79			ThunkInner::Errored(e) => return Err(e.clone()),80			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),81			ThunkInner::Waiting(..) => (),82		};83		let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)84		else {85			unreachable!();86		};87		let new_value = match value.0.get() {88			Ok(v) => v,89			Err(e) => {90				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());91				return Err(e);92			}93		};94		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());95		Ok(new_value)96	}97}9899pub trait ThunkMapper<Input>: Trace {100	type Output;101	fn map(self, from: Input) -> Result<Self::Output>;102}103impl<Input> Thunk<Input>104where105	Input: Trace + Clone,106{107	pub fn map<M>(self, mapper: M) -> Thunk<M::Output>108	where109		M: ThunkMapper<Input>,110		M::Output: Trace,111	{112		#[derive(Trace)]113		struct Mapped<Input: Trace, Mapper: Trace> {114			inner: Thunk<Input>,115			mapper: Mapper,116		}117		impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>118		where119			Input: Trace + Clone,120			Mapper: ThunkMapper<Input>,121		{122			type Output = Mapper::Output;123124			fn get(self: Box<Self>) -> Result<Self::Output> {125				let value = self.inner.evaluate()?;126				let mapped = self.mapper.map(value)?;127				Ok(mapped)128			}129		}130131		Thunk::new(Mapped::<Input, M> {132			inner: self,133			mapper,134		})135	}136}137138impl<T: Trace> From<Result<T>> for Thunk<T> {139	fn from(value: Result<T>) -> Self {140		match value {141			Ok(o) => Self::evaluated(o),142			Err(e) => Self::errored(e),143		}144	}145}146impl<T, V: Trace> From<T> for Thunk<V>147where148	T: ThunkValue<Output = V>,149{150	fn from(value: T) -> Self {151		Self::new(value)152	}153}154155impl<T: Trace + Default> Default for Thunk<T> {156	fn default() -> Self {157		Self::evaluated(T::default())158	}159}160161type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);162163#[derive(Trace, Clone)]164pub struct CachedUnbound<I, T>165where166	I: Unbound<Bound = T>,167	T: Trace,168{169	cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,170	value: I,171}172impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {173	pub fn new(value: I) -> Self {174		Self {175			cache: Cc::new(RefCell::new(GcHashMap::new())),176			value,177		}178	}179}180impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {181	type Bound = T;182	fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {183		let cache_key = (184			sup.as_ref().map(|s| s.clone().downgrade()),185			this.as_ref().map(|t| t.clone().downgrade()),186		);187		{188			if let Some(t) = self.cache.borrow().get(&cache_key) {189				return Ok(t.clone());190			}191		}192		let bound = self.value.bind(sup, this)?;193194		{195			let mut cache = self.cache.borrow_mut();196			cache.insert(cache_key, bound.clone());197		}198199		Ok(bound)200	}201}202203impl<T: Debug + Trace> Debug for Thunk<T> {204	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {205		write!(f, "Lazy")206	}207}208impl<T: Trace> PartialEq for Thunk<T> {209	fn eq(&self, other: &Self) -> bool {210		Cc::ptr_eq(&self.0, &other.0)211	}212}213214/// Represents a Jsonnet value, which can be sliced or indexed (string or array).215#[allow(clippy::module_name_repetitions)]216pub enum IndexableVal {217	/// String.218	Str(IStr),219	/// Array.220	Arr(ArrValue),221}222impl IndexableVal {223	pub fn is_empty(&self) -> bool {224		match self {225			Self::Str(s) => s.is_empty(),226			Self::Arr(s) => s.is_empty(),227		}228	}229230	pub fn to_array(self) -> ArrValue {231		match self {232			Self::Str(s) => ArrValue::chars(s.chars()),233			Self::Arr(arr) => arr,234		}235	}236	/// Slice the value.237	///238	/// # Implementation239	///240	/// For strings, will create a copy of specified interval.241	///242	/// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.243	pub fn slice(244		self,245		index: Option<i32>,246		end: Option<i32>,247		step: Option<BoundedUsize<1, { i32::MAX as usize }>>,248	) -> Result<Self> {249		match &self {250			Self::Str(s) => {251				let mut computed_len = None;252				let mut get_len = || {253					computed_len.map_or_else(254						|| {255							let len = s.chars().count();256							let _ = computed_len.insert(len);257							len258						},259						|len| len,260					)261				};262				let mut get_idx = |pos: Option<i32>, default| {263					match pos {264						Some(v) if v < 0 => get_len().saturating_sub((-v) as usize),265						// No need to clamp, as iterator interface is used266						Some(v) => v as usize,267						None => default,268					}269				};270271				let index = get_idx(index, 0);272				let end = get_idx(end, usize::MAX);273				let step = step.as_deref().copied().unwrap_or(1);274275				if index >= end {276					return Ok(Self::Str("".into()));277				}278279				Ok(Self::Str(280					(s.chars()281						.skip(index)282						.take(end - index)283						.step_by(step)284						.collect::<String>())285					.into(),286				))287			}288			Self::Arr(arr) => Ok(Self::Arr(arr.clone().slice(289				index,290				end,291				step.map(|v| NonZeroU32::new(v.value() as u32).expect("bounded != 0")),292			))),293		}294	}295}296297#[derive(Debug, Clone, Trace)]298pub enum StrValue {299	Flat(IStr),300	Tree(Rc<(StrValue, StrValue, usize)>),301}302impl StrValue {303	pub fn concat(a: Self, b: Self) -> Self {304		// TODO: benchmark for an optimal value, currently just a arbitrary choice305		const STRING_EXTEND_THRESHOLD: usize = 100;306307		if a.is_empty() {308			b309		} else if b.is_empty() {310			a311		} else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {312			Self::Flat(format!("{a}{b}").into())313		} else {314			let len = a.len() + b.len();315			Self::Tree(Rc::new((a, b, len)))316		}317	}318	pub fn into_flat(self) -> IStr {319		#[cold]320		fn write_buf(s: &StrValue, out: &mut String) {321			match s {322				StrValue::Flat(f) => out.push_str(f),323				StrValue::Tree(t) => {324					write_buf(&t.0, out);325					write_buf(&t.1, out);326				}327			}328		}329		match self {330			Self::Flat(f) => f,331			Self::Tree(_) => {332				let mut buf = String::with_capacity(self.len());333				write_buf(&self, &mut buf);334				buf.into()335			}336		}337	}338	pub fn len(&self) -> usize {339		match self {340			Self::Flat(v) => v.len(),341			Self::Tree(t) => t.2,342		}343	}344	pub fn is_empty(&self) -> bool {345		match self {346			Self::Flat(v) => v.is_empty(),347			// Can't create non-flat empty string348			Self::Tree(_) => false,349		}350	}351}352impl<T> From<T> for StrValue353where354	IStr: From<T>,355{356	fn from(value: T) -> Self {357		Self::Flat(IStr::from(value))358	}359}360impl Display for StrValue {361	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {362		match self {363			Self::Flat(v) => write!(f, "{v}"),364			Self::Tree(t) => {365				write!(f, "{}", t.0)?;366				write!(f, "{}", t.1)367			}368		}369	}370}371impl PartialEq for StrValue {372	// False positive, into_flat returns not StrValue, but IStr, thus no infinite recursion here.373	#[allow(clippy::unconditional_recursion)]374	fn eq(&self, other: &Self) -> bool {375		let a = self.clone().into_flat();376		let b = other.clone().into_flat();377		a == b378	}379}380impl Eq for StrValue {}381impl PartialOrd for StrValue {382	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {383		Some(self.cmp(other))384	}385}386impl Ord for StrValue {387	fn cmp(&self, other: &Self) -> std::cmp::Ordering {388		let a = self.clone().into_flat();389		let b = other.clone().into_flat();390		a.cmp(&b)391	}392}393394/// Represents any valid Jsonnet value.395#[derive(Debug, Clone, Trace, Default)]396pub enum Val {397	/// Represents a Jsonnet boolean.398	Bool(bool),399	/// Represents a Jsonnet null value.400	#[default]401	Null,402	/// Represents a Jsonnet string.403	Str(StrValue),404	/// Represents a Jsonnet number.405	/// Should be finite, and not NaN406	/// This restriction isn't enforced by enum, as enum field can't be marked as private407	Num(f64),408	/// Experimental bigint409	#[cfg(feature = "exp-bigint")]410	BigInt(#[trace(skip)] Box<num_bigint::BigInt>),411	/// Represents a Jsonnet array.412	Arr(ArrValue),413	/// Represents a Jsonnet object.414	Obj(ObjValue),415	/// Represents a Jsonnet function.416	Func(FuncVal),417}418419#[cfg(target_pointer_width = "64")]420static_assertions::assert_eq_size!(Val, [u8; 24]);421422impl From<IndexableVal> for Val {423	fn from(v: IndexableVal) -> Self {424		match v {425			IndexableVal::Str(s) => Self::string(s),426			IndexableVal::Arr(a) => Self::Arr(a),427		}428	}429}430431impl Val {432	pub const fn as_bool(&self) -> Option<bool> {433		match self {434			Self::Bool(v) => Some(*v),435			_ => None,436		}437	}438	pub const fn as_null(&self) -> Option<()> {439		match self {440			Self::Null => Some(()),441			_ => None,442		}443	}444	pub fn as_str(&self) -> Option<IStr> {445		match self {446			Self::Str(s) => Some(s.clone().into_flat()),447			_ => None,448		}449	}450	pub const fn as_num(&self) -> Option<f64> {451		match self {452			Self::Num(n) => Some(*n),453			_ => None,454		}455	}456	pub fn as_arr(&self) -> Option<ArrValue> {457		match self {458			Self::Arr(a) => Some(a.clone()),459			_ => None,460		}461	}462	pub fn as_obj(&self) -> Option<ObjValue> {463		match self {464			Self::Obj(o) => Some(o.clone()),465			_ => None,466		}467	}468	pub fn as_func(&self) -> Option<FuncVal> {469		match self {470			Self::Func(f) => Some(f.clone()),471			_ => None,472		}473	}474475	/// Creates `Val::Num` after checking for numeric overflow.476	/// As numbers are `f64`, we can just check for their finity.477	pub fn new_checked_num(num: f64) -> Result<Self> {478		if num.is_finite() {479			Ok(Self::Num(num))480		} else {481			bail!("overflow")482		}483	}484485	pub const fn value_type(&self) -> ValType {486		match self {487			Self::Str(..) => ValType::Str,488			Self::Num(..) => ValType::Num,489			#[cfg(feature = "exp-bigint")]490			Self::BigInt(..) => ValType::BigInt,491			Self::Arr(..) => ValType::Arr,492			Self::Obj(..) => ValType::Obj,493			Self::Bool(_) => ValType::Bool,494			Self::Null => ValType::Null,495			Self::Func(..) => ValType::Func,496		}497	}498499	pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {500		fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {501			manifest.manifest(val.clone())502		}503		manifest_dyn(self, &format)504	}505506	pub fn to_string(&self) -> Result<IStr> {507		Ok(match self {508			Self::Bool(true) => "true".into(),509			Self::Bool(false) => "false".into(),510			Self::Null => "null".into(),511			Self::Str(s) => s.clone().into_flat(),512			_ => self.manifest(ToStringFormat).map(IStr::from)?,513		})514	}515516	pub fn into_indexable(self) -> Result<IndexableVal> {517		Ok(match self {518			Self::Str(s) => IndexableVal::Str(s.into_flat()),519			Self::Arr(arr) => IndexableVal::Arr(arr),520			_ => bail!(ValueIsNotIndexable(self.value_type())),521		})522	}523524	pub fn function(function: impl Into<FuncVal>) -> Self {525		Self::Func(function.into())526	}527	pub fn string(string: impl Into<StrValue>) -> Self {528		Self::Str(string.into())529	}530}531532impl From<IStr> for Val {533	fn from(value: IStr) -> Self {534		Self::string(value)535	}536}537impl From<String> for Val {538	fn from(value: String) -> Self {539		Self::string(value)540	}541}542impl From<&str> for Val {543	fn from(value: &str) -> Self {544		Self::string(value)545	}546}547impl From<ObjValue> for Val {548	fn from(value: ObjValue) -> Self {549		Self::Obj(value)550	}551}552553const fn is_function_like(val: &Val) -> bool {554	matches!(val, Val::Func(_))555}556557/// Native implementation of `std.primitiveEquals`558pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {559	Ok(match (val_a, val_b) {560		(Val::Bool(a), Val::Bool(b)) => a == b,561		(Val::Null, Val::Null) => true,562		(Val::Str(a), Val::Str(b)) => a == b,563		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,564		#[cfg(feature = "exp-bigint")]565		(Val::BigInt(a), Val::BigInt(b)) => a == b,566		(Val::Arr(_), Val::Arr(_)) => {567			bail!("primitiveEquals operates on primitive types, got array")568		}569		(Val::Obj(_), Val::Obj(_)) => {570			bail!("primitiveEquals operates on primitive types, got object")571		}572		(a, b) if is_function_like(a) && is_function_like(b) => {573			bail!("cannot test equality of functions")574		}575		(_, _) => false,576	})577}578579/// Native implementation of `std.equals`580pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {581	if val_a.value_type() != val_b.value_type() {582		return Ok(false);583	}584	match (val_a, val_b) {585		(Val::Arr(a), Val::Arr(b)) => {586			if ArrValue::ptr_eq(a, b) {587				return Ok(true);588			}589			if a.len() != b.len() {590				return Ok(false);591			}592			for (a, b) in a.iter().zip(b.iter()) {593				if !equals(&a?, &b?)? {594					return Ok(false);595				}596			}597			Ok(true)598		}599		(Val::Obj(a), Val::Obj(b)) => {600			if ObjValue::ptr_eq(a, b) {601				return Ok(true);602			}603			let fields = a.fields(604				#[cfg(feature = "exp-preserve-order")]605				false,606			);607			if fields608				!= b.fields(609					#[cfg(feature = "exp-preserve-order")]610					false,611				) {612				return Ok(false);613			}614			for field in fields {615				if !equals(616					&a.get(field.clone())?.expect("field exists"),617					&b.get(field)?.expect("field exists"),618				)? {619					return Ok(false);620				}621			}622			Ok(true)623		}624		(a, b) => Ok(primitive_equals(a, b)?),625	}626}
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	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}
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -275,7 +275,7 @@
 	if arr.is_empty() {
 		return eval_on_empty(onEmpty);
 	}
-	Ok(Val::Num(arr.iter().sum::<f64>() / (arr.len() as f64)))
+	Ok(Val::try_num(arr.iter().sum::<f64>() / (arr.len() as f64))?)
 }
 
 #[builtin]
modifiedcrates/jrsonnet-stdlib/src/operator.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/operator.rs
+++ b/crates/jrsonnet-stdlib/src/operator.rs
@@ -6,12 +6,12 @@
 	operator::evaluate_mod_op,
 	stdlib::std_format,
 	typed::{Either, Either2},
-	val::{equals, primitive_equals},
+	val::{equals, primitive_equals, NumValue},
 	IStr, Result, Val,
 };
 
 #[builtin]
-pub fn builtin_mod(a: Either![f64, IStr], b: Val) -> Result<Val> {
+pub fn builtin_mod(a: Either![NumValue, IStr], b: Val) -> Result<Val> {
 	use Either2::*;
 	evaluate_mod_op(
 		&match a {
modifiedcrates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -20,20 +20,6 @@
 	Unknown,
 }
 
-#[derive(PartialEq)]
-struct NonNaNf64(f64);
-impl PartialOrd for NonNaNf64 {
-	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-		Some(self.cmp(other))
-	}
-}
-impl Eq for NonNaNf64 {}
-impl Ord for NonNaNf64 {
-	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-		self.0.partial_cmp(&other.0).expect("non nan")
-	}
-}
-
 fn get_sort_type<T>(values: &[T], key_getter: impl Fn(&T) -> &Val) -> Result<SortKeyType> {
 	let mut sort_type = SortKeyType::Unknown;
 	for i in values {
@@ -56,7 +42,7 @@
 	let sort_type = get_sort_type(&values, |k| k)?;
 	match sort_type {
 		SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
-			Val::Num(n) => NonNaNf64(*n),
+			Val::Num(n) => *n,
 			_ => unreachable!(),
 		}),
 		SortKeyType::String => values.sort_unstable_by_key(|v| match v {
@@ -95,7 +81,7 @@
 	let sort_type = get_sort_type(&vk, |v| &v.1)?;
 	match sort_type {
 		SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
-			Val::Num(n) => NonNaNf64(n),
+			Val::Num(n) => n,
 			_ => unreachable!(),
 		}),
 		SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/strings.rs
+++ b/crates/jrsonnet-stdlib/src/strings.rs
@@ -116,7 +116,9 @@
 		.enumerate()
 	{
 		if &strb[i..i + pat.len()] == pat {
-			out.push(Val::Num(ch_idx as f64));
+			out.push(Val::Num(
+				ch_idx.try_into().expect("unrealisticly long string"),
+			));
 		}
 	}
 	out.into()