difftreelog
fix enforce Val::Num finityness at type level
in: master
13 files changed
crates/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> {
crates/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 {
crates/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)
}),
crates/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
crates/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> {
crates/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?"
crates/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!(),
+ }
+ }
+}
crates/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 {
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use 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}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}crates/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]
crates/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 {
crates/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 {
crates/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()