From 0e2224254bcfae75c5c86ee1047d0deac3cead5a Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sun, 19 May 2024 18:26:23 +0000 Subject: [PATCH] fix: enforce Val::Num finityness at type level --- --- 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 { - 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 { - 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 { +pub struct MappedArray { inner: ArrValue, cached: Cc>>>, mapper: FuncVal, } -impl MappedArray { +impl MappedArray { 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 { - if WithIndex { + if WITH_INDEX { self.mapper.evaluate_simple(&(index, value), false) } else { self.mapper.evaluate_simple(&(value,), false) } } } -impl ArrayLike for MappedArray { +impl ArrayLike for MappedArray { fn len(&self) -> usize { self.cached.borrow().len() } @@ -493,12 +493,12 @@ } fn get_lazy(&self, index: usize) -> Option> { #[derive(Trace)] - struct ArrayElement { - arr_thunk: MappedArray, + struct ArrayElement { + arr_thunk: MappedArray, index: usize, } - impl ThunkValue for ArrayElement { + impl ThunkValue for ArrayElement { type Output = Val; fn get(self: Box) -> Result { --- 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 for Error { + fn from(_value: Infallible) -> Self { + unreachable!() + } +} + /// Single stack trace frame #[derive(Clone, Debug, Trace)] pub struct StackTraceElement { --- 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::() .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) }), --- 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 --- 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(self, v: bool) -> Result where - E: serde::de::Error, + E: de::Error, { Ok(Val::Bool(v)) } fn visit_f64(self, v: f64) -> Result 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(self, v: &str) -> Result where - E: serde::de::Error, + E: de::Error, { Ok(Val::string(v)) } @@ -67,27 +67,27 @@ // } fn visit_i64(self, v: i64) -> Result 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(self, v: u64) -> Result 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(self, v: &[u8]) -> Result where - E: serde::de::Error, + E: de::Error, { Ok(Val::Arr(ArrValue::bytes(v.into()))) } fn visit_none(self) -> Result where - E: serde::de::Error, + E: de::Error, { Ok(Val::Null) } @@ -100,7 +100,7 @@ fn visit_unit(self) -> Result where - E: serde::de::Error, + E: de::Error, { Ok(Val::Null) } @@ -114,7 +114,7 @@ fn visit_seq(self, mut seq: A) -> Result 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(self, mut map: A) -> Result 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 { - Ok(Val::Num(f64::from(v))) + Ok(Val::Num(v.into())) } fn serialize_i16(self, v: i16) -> Result { - Ok(Val::Num(f64::from(v))) + Ok(Val::Num(v.into())) } fn serialize_i32(self, v: i32) -> Result { - Ok(Val::Num(f64::from(v))) + Ok(Val::Num(v.into())) } fn serialize_i64(self, v: i64) -> Result { @@ -465,15 +466,15 @@ } fn serialize_u8(self, v: u8) -> Result { - Ok(Val::Num(f64::from(v))) + Ok(Val::Num(v.into())) } fn serialize_u16(self, v: u16) -> Result { - Ok(Val::Num(f64::from(v))) + Ok(Val::Num(v.into())) } fn serialize_u32(self, v: u32) -> Result { - Ok(Val::Num(f64::from(v))) + Ok(Val::Num(v.into())) } fn serialize_u64(self, v: u64) -> Result { @@ -481,11 +482,11 @@ } fn serialize_f32(self, v: f32) -> Result { - Ok(Val::Num(f64::from(v))) + Ok(Val::try_num(f64::from(v))?) } fn serialize_f64(self, v: f64) -> Result { - Ok(Val::Num(v)) + Ok(Val::try_num(v)?) } fn serialize_char(self, v: char) -> Result { --- 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?" --- 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 @@ ::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 { - Ok(Val::Num(value as f64)) + Ok(Val::Num(value.into())) } } )*}; @@ -187,6 +188,7 @@ ::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 { - 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 { - Ok(Val::Num(value)) + Ok(Val::try_num(value)?) } fn from_untyped(value: Val) -> Result { ::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 { - Ok(Val::Num(value.0)) + Ok(Val::try_num(value.0)?) } fn from_untyped(value: Val) -> Result { ::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 { - 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 { ::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 { - Ok(Val::Num(-1.0)) + Ok(Val::Num(NumValue::new(-1.0).expect("finite"))) } fn from_untyped(value: Val) -> Result { @@ -679,3 +679,19 @@ )) } } + +impl Typed for NumValue { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num); + + fn into_untyped(typed: Self) -> Result { + Ok(Val::Num(typed)) + } + + fn from_untyped(untyped: Val) -> Result { + Self::TYPE.check(&untyped)?; + match untyped { + Val::Num(v) => Ok(v), + _ => unreachable!(), + } + } +} --- 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 { --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,14 +1,18 @@ use std::{ cell::RefCell, + cmp::Ordering, fmt::{self, Debug, Display}, mem::replace, num::NonZeroU32, + ops::Deref, rc::Rc, }; +use derivative::Derivative; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; use jrsonnet_types::ValType; +use thiserror::Error; pub use crate::arr::{ArrValue, ArrayLike}; use crate::{ @@ -379,18 +383,127 @@ } impl Eq for StrValue {} impl PartialOrd for StrValue { - fn partial_cmp(&self, other: &Self) -> Option { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for StrValue { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> Ordering { let a = self.clone().into_flat(); let b = other.clone().into_flat(); a.cmp(&b) } } +/// Represents jsonnet number +/// Jsonnet numbers are finite f64, with NaNs disallowed +#[derive(Trace, Clone, Copy, Derivative)] +#[derivative(Debug = "transparent")] +#[repr(transparent)] +pub struct NumValue(f64); +impl NumValue { + /// Creates a [`NumValue`], if value is finite and not NaN + pub fn new(v: f64) -> Option { + if !v.is_finite() { + return None; + } + Some(Self(v)) + } + pub const fn get(&self) -> f64 { + self.0 + } +} +impl PartialEq for NumValue { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} +impl Eq for NumValue {} +impl Ord for NumValue { + fn cmp(&self, other: &Self) -> Ordering { + // Can't use `total_cmp`: its behavior for `-0` and `0` + // is not following wanted. + self.0.partial_cmp(&other.0).expect("NaNs are disallowed") + } +} +impl PartialOrd for NumValue { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Display for NumValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + Display::fmt(&self.0, f) + } +} +impl Deref for NumValue { + type Target = f64; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} +macro_rules! impl_num { + ($($ty:ty),+) => {$( + impl From<$ty> for NumValue { + fn from(value: $ty) -> Self { + Self(value.into()) + } + } + )+}; +} +impl_num!(i8, u8, i16, u16, i32, u32); + +#[derive(Clone, Copy, Debug, Error, Trace)] +pub enum ConvertNumValueError { + #[error("overflow")] + Overflow, + #[error("underflow")] + Underflow, + #[error("non-finite")] + NonFinite, +} +impl From for Error { + fn from(e: ConvertNumValueError) -> Self { + Self::new(e.into()) + } +} + +macro_rules! impl_try_num { + ($($ty:ty),+) => {$( + impl TryFrom<$ty> for NumValue { + type Error = ConvertNumValueError; + fn try_from(value: $ty) -> Result { + use crate::typed::conversions::{MIN_SAFE_INTEGER, MAX_SAFE_INTEGER}; + let value = value as f64; + if value < MIN_SAFE_INTEGER { + return Err(ConvertNumValueError::Underflow) + } else if value > MAX_SAFE_INTEGER { + return Err(ConvertNumValueError::Overflow) + } + // Number is finite. + Ok(Self(value)) + } + } + )+}; +} +impl_try_num!(usize, isize, i64, u64); + +impl TryFrom for NumValue { + type Error = ConvertNumValueError; + + fn try_from(value: f64) -> Result { + Self::new(value).ok_or(ConvertNumValueError::NonFinite) + } +} +impl TryFrom for NumValue { + type Error = ConvertNumValueError; + + fn try_from(value: f32) -> Result { + Self::new(f64::from(value)).ok_or(ConvertNumValueError::NonFinite) + } +} + /// Represents any valid Jsonnet value. #[derive(Debug, Clone, Trace, Default)] pub enum Val { @@ -404,7 +517,7 @@ /// Represents a Jsonnet number. /// Should be finite, and not NaN /// This restriction isn't enforced by enum, as enum field can't be marked as private - Num(f64), + Num(NumValue), /// Experimental bigint #[cfg(feature = "exp-bigint")] BigInt(#[trace(skip)] Box), @@ -449,7 +562,7 @@ } pub const fn as_num(&self) -> Option { match self { - Self::Num(n) => Some(*n), + Self::Num(n) => Some(n.get()), _ => None, } } @@ -472,16 +585,6 @@ } } - /// Creates `Val::Num` after checking for numeric overflow. - /// As numbers are `f64`, we can just check for their finity. - pub fn new_checked_num(num: f64) -> Result { - if num.is_finite() { - Ok(Self::Num(num)) - } else { - bail!("overflow") - } - } - pub const fn value_type(&self) -> ValType { match self { Self::Str(..) => ValType::Str, @@ -527,6 +630,15 @@ pub fn string(string: impl Into) -> Self { Self::Str(string.into()) } + pub fn num(num: impl Into) -> Self { + Self::Num(num.into()) + } + pub fn try_num(num: V) -> Result + where + NumValue: TryFrom, + { + Ok(Self::Num(num.try_into()?)) + } } impl From for Val { @@ -560,7 +672,7 @@ (Val::Bool(a), Val::Bool(b)) => a == b, (Val::Null, Val::Null) => true, (Val::Str(a), Val::Str(b)) => a == b, - (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON, + (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON, #[cfg(feature = "exp-bigint")] (Val::BigInt(a), Val::BigInt(b)) => a == b, (Val::Arr(_), Val::Arr(_)) => { --- 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::() / (arr.len() as f64))) + Ok(Val::try_num(arr.iter().sum::() / (arr.len() as f64))?) } #[builtin] --- 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 { +pub fn builtin_mod(a: Either![NumValue, IStr], b: Val) -> Result { use Either2::*; evaluate_mod_op( &match a { --- 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 { - 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(values: &[T], key_getter: impl Fn(&T) -> &Val) -> Result { 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 { --- 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() -- gitstuff