difftreelog
fix enforce Val::Num finityness at type level
in: master
13 files changed
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth120 }120 }121121122 fn get_cheap(&self, index: usize) -> Option<Val> {122 fn get_cheap(&self, index: usize) -> Option<Val> {123 self.0.get(index).map(|v| Val::Num(f64::from(*v)))123 self.0.get(index).map(|v| Val::Num((*v).into()))124 }124 }125 fn is_cheap(&self) -> bool {125 fn is_cheap(&self) -> bool {126 true126 true399 }399 }400400401 fn get_cheap(&self, index: usize) -> Option<Val> {401 fn get_cheap(&self, index: usize) -> Option<Val> {402 self.range().nth(index).map(|i| Val::Num(f64::from(i)))402 self.range().nth(index).map(|i| Val::Num(i.into()))403 }403 }404 fn is_cheap(&self) -> bool {404 fn is_cheap(&self) -> bool {405 true405 true430}430}431431432#[derive(Trace, Debug, Clone)]432#[derive(Trace, Debug, Clone)]433pub struct MappedArray<const WithIndex: bool> {433pub struct MappedArray<const WITH_INDEX: bool> {434 inner: ArrValue,434 inner: ArrValue,435 cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,435 cached: Cc<RefCell<Vec<ArrayThunk<()>>>>,436 mapper: FuncVal,436 mapper: FuncVal,437}437}438impl<const WithIndex: bool> MappedArray<WithIndex> {438impl<const WITH_INDEX: bool> MappedArray<WITH_INDEX> {439 pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {439 pub fn new(inner: ArrValue, mapper: FuncVal) -> Self {440 let len = inner.len();440 let len = inner.len();441 Self {441 Self {445 }445 }446 }446 }447 fn evaluate(&self, index: usize, value: Val) -> Result<Val> {447 fn evaluate(&self, index: usize, value: Val) -> Result<Val> {448 if WithIndex {448 if WITH_INDEX {449 self.mapper.evaluate_simple(&(index, value), false)449 self.mapper.evaluate_simple(&(index, value), false)450 } else {450 } else {451 self.mapper.evaluate_simple(&(value,), false)451 self.mapper.evaluate_simple(&(value,), false)452 }452 }453 }453 }454}454}455impl<const WithIndex: bool> ArrayLike for MappedArray<WithIndex> {455impl<const WITH_INDEX: bool> ArrayLike for MappedArray<WITH_INDEX> {456 fn len(&self) -> usize {456 fn len(&self) -> usize {457 self.cached.borrow().len()457 self.cached.borrow().len()458 }458 }493 }493 }494 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {494 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {495 #[derive(Trace)]495 #[derive(Trace)]496 struct ArrayElement<const WithIndex: bool> {496 struct ArrayElement<const WITH_INDEX: bool> {497 arr_thunk: MappedArray<WithIndex>,497 arr_thunk: MappedArray<WITH_INDEX>,498 index: usize,498 index: usize,499 }499 }500500501 impl<const WithIndex: bool> ThunkValue for ArrayElement<WithIndex> {501 impl<const WITH_INDEX: bool> ThunkValue for ArrayElement<WITH_INDEX> {502 type Output = Val;502 type Output = Val;503503504 fn get(self: Box<Self>) -> Result<Self::Output> {504 fn get(self: Box<Self>) -> Result<Self::Output> {crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth1use std::{1use std::{2 cmp::Ordering,2 cmp::Ordering, convert::Infallible, fmt::{Debug, Display}, path::PathBuf3 fmt::{Debug, Display},4 path::PathBuf,5};3};647use jrsonnet_gcmodule::Trace;5use jrsonnet_gcmodule::Trace;14 function::{builtin::ParamDefault, CallLocation},12 function::{builtin::ParamDefault, CallLocation},15 stdlib::format::FormatError,13 stdlib::format::FormatError,16 typed::TypeLocError,14 typed::TypeLocError,15 val::ConvertNumValueError,17 ObjValue,16 ObjValue,18};17};1918236 #[error("invalid unicode codepoint: {0}")]235 #[error("invalid unicode codepoint: {0}")]237 InvalidUnicodeCodepointGot(u32),236 InvalidUnicodeCodepointGot(u32),237238 #[error("convert num value: {0}")]239 ConvertNumValue(#[from] ConvertNumValueError),238240239 #[error("format error: {0}")]241 #[error("format error: {0}")]240 Format(#[from] FormatError),242 Format(#[from] FormatError),259 }261 }260}262}263264impl From<Infallible> for Error {265 fn from(_value: Infallible) -> Self {266 unreachable!()267 }268}261269262/// Single stack trace frame270/// Single stack trace frame263#[derive(Clone, Debug, Trace)]271#[derive(Clone, Debug, Trace)]crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth17 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},17 evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},18 function::{CallLocation, FuncDesc, FuncVal},18 function::{CallLocation, FuncDesc, FuncVal},19 typed::Typed,19 typed::Typed,20 val::{CachedUnbound, IndexableVal, StrValue, Thunk, ThunkValue},20 val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk, ThunkValue},21 Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,21 Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,22 ResultExt, State, Unbound, Val,22 ResultExt, State, Unbound, Val,23};23};37 }37 }38 Some(match &*expr.0 {38 Some(match &*expr.0 {39 Expr::Str(s) => Val::string(s.clone()),39 Expr::Str(s) => Val::string(s.clone()),40 Expr::Num(n) => Val::Num(*n),40 Expr::Num(n) => Val::Num(NumValue::new(*n).expect("parser will not allow non-finite values")),41 Expr::Literal(LiteralType::False) => Val::Bool(false),41 Expr::Literal(LiteralType::False) => Val::Bool(false),42 Expr::Literal(LiteralType::True) => Val::Bool(true),42 Expr::Literal(LiteralType::True) => Val::Bool(true),43 Expr::Literal(LiteralType::Null) => Val::Null,43 Expr::Literal(LiteralType::Null) => Val::Null,438 Literal(LiteralType::Null) => Val::Null,438 Literal(LiteralType::Null) => Val::Null,439 Parened(e) => evaluate(ctx, e)?,439 Parened(e) => evaluate(ctx, e)?,440 Str(v) => Val::string(v.clone()),440 Str(v) => Val::string(v.clone()),441 Num(v) => Val::new_checked_num(*v)?,441 Num(v) => Val::try_num(*v)?,442 // I have tried to remove special behavior from super by implementing standalone-super442 // I have tried to remove special behavior from super by implementing standalone-super443 // expresion, but looks like this case still needs special treatment.443 // expresion, but looks like this case still needs special treatment.444 //444 //530 n.value_type(),530 n.value_type(),531 )),531 )),532 (Val::Arr(v), Val::Num(n)) => {532 (Val::Arr(v), Val::Num(n)) => {533 let n = n.get();533 if n.fract() > f64::EPSILON {534 if n.fract() > f64::EPSILON {534 bail!(FractionalIndex)535 bail!(FractionalIndex)535 }536 }553 .clone()554 .clone()554 .into_flat()555 .into_flat()555 .chars()556 .chars()556 .skip(n as usize)557 .skip(n.get() as usize)557 .take(1)558 .take(1)558 .collect::<String>()559 .collect::<String>()559 .into();560 .into();560 if v.is_empty() {561 if v.is_empty() {561 let size = s.into_flat().chars().count();562 let size = s.into_flat().chars().count();562 bail!(StringBoundsError(n as usize, size))563 bail!(StringBoundsError(n.get() as usize, size))563 }564 }564 StrValue::Flat(v)565 StrValue::Flat(v)565 }),566 }),crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth17 use UnaryOpType::*;17 use UnaryOpType::*;18 use Val::*;18 use Val::*;19 Ok(match (op, b) {19 Ok(match (op, b) {20 (Plus, Num(n)) => Num(*n),20 (Plus, Num(n)) => Val::Num(*n),21 (Minus, Num(n)) => Num(-*n),21 (Minus, Num(n)) => Val::try_num(-n.get())?,22 (Not, Bool(v)) => Bool(!v),22 (Not, Bool(v)) => Bool(!v),23 (BitNot, Num(n)) => Num(!(*n as i64) as f64),23 (BitNot, Num(n)) => Val::try_num(!(n.get() as i64) as f64)?,24 (op, o) => bail!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())),24 (op, o) => bail!(UnaryOperatorDoesNotOperateOnType(op, o.value_type())),25 })25 })26}26}40 (Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),40 (Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),41 (Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),41 (Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),424243 (Num(v1), Num(v2)) => Val::new_checked_num(v1 + v2)?,43 (Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,44 #[cfg(feature = "exp-bigint")]44 #[cfg(feature = "exp-bigint")]45 (BigInt(a), BigInt(b)) => BigInt(Box::new(&**a + &**b)),45 (BigInt(a), BigInt(b)) => BigInt(Box::new(&**a + &**b)),46 _ => bail!(BinaryOperatorDoesNotOperateOnValues(46 _ => bail!(BinaryOperatorDoesNotOperateOnValues(55 use Val::*;55 use Val::*;56 match (a, b) {56 match (a, b) {57 (Num(a), Num(b)) => {57 (Num(a), Num(b)) => {58 if *b == 0.0 {58 if b.get() == 0.0 {59 bail!(DivisionByZero)59 bail!(DivisionByZero)60 }60 }61 Ok(Num(a % b))61 Ok(Val::try_num(a.get() % b.get())?)62 }62 }63 (Str(str), vals) => {63 (Str(str), vals) => {64 String::into_untyped(std_format(&str.clone().into_flat(), vals.clone())?)64 String::into_untyped(std_format(&str.clone().into_flat(), vals.clone())?)143 (Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone().into_flat(), true)),143 (Str(a), In, Obj(obj)) => Bool(obj.has_field_ex(a.clone().into_flat(), true)),144 (a, Mod, b) => evaluate_mod_op(a, b)?,144 (a, Mod, b) => evaluate_mod_op(a, b)?,145145146 (Str(v1), Mul, Num(v2)) => Val::string(v1.to_string().repeat(*v2 as usize)),146 (Str(v1), Mul, Num(v2)) => Val::string(v1.to_string().repeat(v2.get() as usize)),147147148 // Bool X Bool148 // Bool X Bool149 (Bool(a), And, Bool(b)) => Bool(*a && *b),149 (Bool(a), And, Bool(b)) => Bool(*a && *b),150 (Bool(a), Or, Bool(b)) => Bool(*a || *b),150 (Bool(a), Or, Bool(b)) => Bool(*a || *b),151151152 // Num X Num152 // Num X Num153 (Num(v1), Mul, Num(v2)) => Val::new_checked_num(v1 * v2)?,153 (Num(v1), Mul, Num(v2)) => Val::try_num(v1.get() * v2.get())?,154 (Num(v1), Div, Num(v2)) => {154 (Num(v1), Div, Num(v2)) => {155 if *v2 == 0.0 {155 if v2.get() == 0.0 {156 bail!(DivisionByZero)156 bail!(DivisionByZero)157 }157 }158 Val::new_checked_num(v1 / v2)?158 Val::try_num(v1.get() / v2.get())?159 }159 }160160161 (Num(v1), Sub, Num(v2)) => Val::new_checked_num(v1 - v2)?,161 (Num(v1), Sub, Num(v2)) => Val::try_num(v1.get() - v2.get())?,162162163 (Num(v1), BitAnd, Num(v2)) => Num((*v1 as i64 & *v2 as i64) as f64),163 (Num(v1), BitAnd, Num(v2)) => Val::try_num((v1.get() as i64 & v2.get() as i64) as f64)?,164 (Num(v1), BitOr, Num(v2)) => Num((*v1 as i64 | *v2 as i64) as f64),164 (Num(v1), BitOr, Num(v2)) => Val::try_num((v1.get() as i64 | v2.get() as i64) as f64)?,165 (Num(v1), BitXor, Num(v2)) => Num((*v1 as i64 ^ *v2 as i64) as f64),165 (Num(v1), BitXor, Num(v2)) => Val::try_num((v1.get() as i64 ^ v2.get() as i64) as f64)?,166 (Num(v1), Lhs, Num(v2)) => {166 (Num(v1), Lhs, Num(v2)) => {167 if *v2 < 0.0 {167 if v2.get() < 0.0 {168 bail!("shift by negative exponent")168 bail!("shift by negative exponent")169 }169 }170 let exp = ((*v2 as i64) & 63) as u32;170 let exp = ((v2.get() as i64) & 63) as u32;171 Num((*v1 as i64).wrapping_shl(exp) as f64)171 Val::try_num((v1.get() as i64).wrapping_shl(exp) as f64)?172 }172 }173 (Num(v1), Rhs, Num(v2)) => {173 (Num(v1), Rhs, Num(v2)) => {174 if *v2 < 0.0 {174 if v2.get() < 0.0 {175 bail!("shift by negative exponent")175 bail!("shift by negative exponent")176 }176 }177 let exp = ((*v2 as i64) & 63) as u32;177 let exp = ((v2.get() as i64) & 63) as u32;178 Num((*v1 as i64).wrapping_shr(exp) as f64)178 Val::try_num((v1.get() as i64).wrapping_shr(exp) as f64)?179 }179 }180180181 // Bigint X Bigint181 // Bigint X Bigintcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth223use jrsonnet_interner::IStr;3use jrsonnet_interner::IStr;4use serde::{4use serde::{5 de::Visitor,5 de::{self, Visitor},6 ser::{6 ser::{7 Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,7 Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,8 SerializeTupleStruct, SerializeTupleVariant,8 SerializeTupleStruct, SerializeTupleVariant,11};11};121213use crate::{13use crate::{14 arr::ArrValue, runtime_error, Error as JrError, ObjValue, ObjValueBuilder, Result, State, Val,14 arr::ArrValue, runtime_error, val::NumValue, Error as JrError, ObjValue, ObjValueBuilder,15 Result, State, Val,15};16};1617373838 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>39 fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>39 where40 where40 E: serde::de::Error,41 E: de::Error,41 {42 {42 Ok(Val::Bool(v))43 Ok(Val::Bool(v))43 }44 }44 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>45 fn visit_f64<E>(self, v: f64) -> Result<Self::Value, E>45 where46 where46 E: serde::de::Error,47 E: de::Error,47 {48 {48 if !v.is_finite() {49 return Err(E::custom("only finite numbers are supported"));49 Ok(Val::Num(NumValue::new(v).ok_or_else(|| {50 }50 E::custom("only finite numbers are supported")51 Ok(Val::Num(v))51 })?))52 }52 }53 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>53 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>54 where54 where55 E: serde::de::Error,55 E: de::Error,56 {56 {57 Ok(Val::string(v))57 Ok(Val::string(v))58 }58 }67 // }67 // }68 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>68 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>69 where69 where70 E: serde::de::Error,70 E: de::Error,71 {71 {72 Ok(Val::Num(v as f64))72 Ok(Val::Num(NumValue::new(v as f64).expect("no overflow")))73 }73 }74 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>74 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>75 where75 where76 E: serde::de::Error,76 E: de::Error,77 {77 {78 Ok(Val::Num(v as f64))78 Ok(Val::Num(NumValue::new(v as f64).expect("no overflow")))79 }79 }808081 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>81 fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>82 where82 where83 E: serde::de::Error,83 E: de::Error,84 {84 {85 Ok(Val::Arr(ArrValue::bytes(v.into())))85 Ok(Val::Arr(ArrValue::bytes(v.into())))86 }86 }878788 fn visit_none<E>(self) -> Result<Self::Value, E>88 fn visit_none<E>(self) -> Result<Self::Value, E>89 where89 where90 E: serde::de::Error,90 E: de::Error,91 {91 {92 Ok(Val::Null)92 Ok(Val::Null)93 }93 }100100101 fn visit_unit<E>(self) -> Result<Self::Value, E>101 fn visit_unit<E>(self) -> Result<Self::Value, E>102 where102 where103 E: serde::de::Error,103 E: de::Error,104 {104 {105 Ok(Val::Null)105 Ok(Val::Null)106 }106 }114114115 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>115 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>116 where116 where117 A: serde::de::SeqAccess<'de>,117 A: de::SeqAccess<'de>,118 {118 {119 let mut out = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);119 let mut out = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);120120127127128 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>128 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>129 where129 where130 A: serde::de::MapAccess<'de>,130 A: de::MapAccess<'de>,131 {131 {132 let mut out = map132 let mut out = map133 .size_hint()133 .size_hint()159 Self::Null => serializer.serialize_none(),159 Self::Null => serializer.serialize_none(),160 Self::Str(s) => serializer.serialize_str(&s.clone().into_flat()),160 Self::Str(s) => serializer.serialize_str(&s.clone().into_flat()),161 Self::Num(n) => {161 Self::Num(n) => {162 let n = n.get();162 if n.fract() == 0.0 {163 if n.fract() == 0.0 {163 let n = *n as i64;164 let n = n as i64;164 serializer.serialize_i64(n)165 serializer.serialize_i64(n)165 } else {166 } else {166 serializer.serialize_f64(*n)167 serializer.serialize_f64(n)167 }168 }168 }169 }169 #[cfg(feature = "exp-bigint")]170 #[cfg(feature = "exp-bigint")]449 }450 }450451451 fn serialize_i8(self, v: i8) -> Result<Val> {452 fn serialize_i8(self, v: i8) -> Result<Val> {452 Ok(Val::Num(f64::from(v)))453 Ok(Val::Num(v.into()))453 }454 }454455455 fn serialize_i16(self, v: i16) -> Result<Val> {456 fn serialize_i16(self, v: i16) -> Result<Val> {456 Ok(Val::Num(f64::from(v)))457 Ok(Val::Num(v.into()))457 }458 }458459459 fn serialize_i32(self, v: i32) -> Result<Val> {460 fn serialize_i32(self, v: i32) -> Result<Val> {460 Ok(Val::Num(f64::from(v)))461 Ok(Val::Num(v.into()))461 }462 }462463463 fn serialize_i64(self, v: i64) -> Result<Val> {464 fn serialize_i64(self, v: i64) -> Result<Val> {464 Ok(Val::Str(v.to_string().into()))465 Ok(Val::Str(v.to_string().into()))465 }466 }466467467 fn serialize_u8(self, v: u8) -> Result<Val> {468 fn serialize_u8(self, v: u8) -> Result<Val> {468 Ok(Val::Num(f64::from(v)))469 Ok(Val::Num(v.into()))469 }470 }470471471 fn serialize_u16(self, v: u16) -> Result<Val> {472 fn serialize_u16(self, v: u16) -> Result<Val> {472 Ok(Val::Num(f64::from(v)))473 Ok(Val::Num(v.into()))473 }474 }474475475 fn serialize_u32(self, v: u32) -> Result<Val> {476 fn serialize_u32(self, v: u32) -> Result<Val> {476 Ok(Val::Num(f64::from(v)))477 Ok(Val::Num(v.into()))477 }478 }478479479 fn serialize_u64(self, v: u64) -> Result<Val> {480 fn serialize_u64(self, v: u64) -> Result<Val> {480 Ok(Val::Str(v.to_string().into()))481 Ok(Val::Str(v.to_string().into()))481 }482 }482483483 fn serialize_f32(self, v: f32) -> Result<Val> {484 fn serialize_f32(self, v: f32) -> Result<Val> {484 Ok(Val::Num(f64::from(v)))485 Ok(Val::try_num(f64::from(v))?)485 }486 }486487487 fn serialize_f64(self, v: f64) -> Result<Val> {488 fn serialize_f64(self, v: f64) -> Result<Val> {488 Ok(Val::Num(v))489 Ok(Val::try_num(v)?)489 }490 }490491491 fn serialize_char(self, v: char) -> Result<Val> {492 fn serialize_char(self, v: char) -> Result<Val> {crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth604 }604 }605 }605 }606 ConvTypeV::Char => match value.clone() {606 ConvTypeV::Char => match value.clone() {607 Val::Num(n) => tmp_out.push(607 Val::Num(n) => {608 let n = n.get();609 tmp_out.push(608 std::char::from_u32(n as u32)610 std::char::from_u32(n as u32)609 .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,611 .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,610 ),612 )613 }611 Val::Str(s) => {614 Val::Str(s) => {612 let s = s.into_flat();615 let s = s.into_flat();613 if s.chars().count() != 1 {616 if s.chars().count() != 1 {786#[cfg(test)]789#[cfg(test)]787pub mod test_format {790pub mod test_format {788 use super::*;791 use super::*;792 use crate::val::NumValue;789793790 #[test]794 #[test]791 fn parse() {795 fn parse() {799 );803 );800 }804 }805806 fn num(v: f64) -> Val {807 Val::Num(NumValue::new(v).expect("finite"))808 }801809802 #[test]810 #[test]803 fn octals() {811 fn octals() {804 assert_eq!(format_arr("%#o", &[Val::Num(8.0)]).unwrap(), "010");812 assert_eq!(format_arr("%#o", &[num(8.0)]).unwrap(), "010");805 assert_eq!(format_arr("%#4o", &[Val::Num(8.0)]).unwrap(), " 010");813 assert_eq!(format_arr("%#4o", &[num(8.0)]).unwrap(), " 010");806 assert_eq!(format_arr("%4o", &[Val::Num(8.0)]).unwrap(), " 10");814 assert_eq!(format_arr("%4o", &[num(8.0)]).unwrap(), " 10");807 assert_eq!(format_arr("%04o", &[Val::Num(8.0)]).unwrap(), "0010");815 assert_eq!(format_arr("%04o", &[num(8.0)]).unwrap(), "0010");808 assert_eq!(format_arr("%+4o", &[Val::Num(8.0)]).unwrap(), " +10");816 assert_eq!(format_arr("%+4o", &[num(8.0)]).unwrap(), " +10");809 assert_eq!(format_arr("%+04o", &[Val::Num(8.0)]).unwrap(), "+010");817 assert_eq!(format_arr("%+04o", &[num(8.0)]).unwrap(), "+010");810 assert_eq!(format_arr("%-4o", &[Val::Num(8.0)]).unwrap(), "10 ");818 assert_eq!(format_arr("%-4o", &[num(8.0)]).unwrap(), "10 ");811 assert_eq!(format_arr("%+-4o", &[Val::Num(8.0)]).unwrap(), "+10 ");819 assert_eq!(format_arr("%+-4o", &[num(8.0)]).unwrap(), "+10 ");812 assert_eq!(format_arr("%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");820 assert_eq!(format_arr("%+-04o", &[num(8.0)]).unwrap(), "+10 ");813 }821 }814822815 #[test]823 #[test]816 fn percent_doesnt_consumes_values() {824 fn percent_doesnt_consumes_values() {817 assert_eq!(825 assert_eq!(818 format_arr(826 format_arr(819 "How much error budget is left looking at our %.3f%% availability gurantees?",827 "How much error budget is left looking at our %.3f%% availability gurantees?",820 &[Val::Num(4.0)]828 &[num(4.0)]821 )829 )822 .unwrap(),830 .unwrap(),823 "How much error budget is left looking at our 4.000% availability gurantees?"831 "How much error budget is left looking at our 4.000% availability gurantees?"crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth10 bail,10 bail,11 function::{native::NativeDesc, FuncDesc, FuncVal},11 function::{native::NativeDesc, FuncDesc, FuncVal},12 typed::CheckType,12 typed::CheckType,13 val::{IndexableVal, StrValue, ThunkMapper},13 val::{IndexableVal, NumValue, StrValue, ThunkMapper},14 ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,14 ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,15};15};1616120 }120 }121}121}122122123const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;123pub const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;124pub const MIN_SAFE_INTEGER: f64 = -MAX_SAFE_INTEGER;124125125macro_rules! impl_int {126macro_rules! impl_int {126 ($($ty:ty)*) => {$(127 ($($ty:ty)*) => {$(131 <Self as Typed>::TYPE.check(&value)?;132 <Self as Typed>::TYPE.check(&value)?;132 match value {133 match value {133 Val::Num(n) => {134 Val::Num(n) => {135 let n = n.get();134 #[allow(clippy::float_cmp)]136 #[allow(clippy::float_cmp)]135 if n.trunc() != n {137 if n.trunc() != n {136 bail!(138 bail!(143 _ => unreachable!(),145 _ => unreachable!(),144 }146 }145 }147 }146 #[allow(clippy::cast_lossless)]147 fn into_untyped(value: Self) -> Result<Val> {148 fn into_untyped(value: Self) -> Result<Val> {148 Ok(Val::Num(value as f64))149 Ok(Val::Num(value.into()))149 }150 }150 }151 }151 )*};152 )*};187 <Self as Typed>::TYPE.check(&value)?;188 <Self as Typed>::TYPE.check(&value)?;188 match value {189 match value {189 Val::Num(n) => {190 Val::Num(n) => {191 let n = n.get();190 #[allow(clippy::float_cmp)]192 #[allow(clippy::float_cmp)]191 if n.trunc() != n {193 if n.trunc() != n {192 bail!(194 bail!(202204203 #[allow(clippy::cast_lossless)]205 #[allow(clippy::cast_lossless)]204 fn into_untyped(value: Self) -> Result<Val> {206 fn into_untyped(value: Self) -> Result<Val> {205 Ok(Val::Num(value.0 as f64))207 Ok(Val::try_num(value.0)?)206 }208 }207 }209 }208 )*};210 )*};220 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);222 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);221223222 fn into_untyped(value: Self) -> Result<Val> {224 fn into_untyped(value: Self) -> Result<Val> {223 Ok(Val::Num(value))225 Ok(Val::try_num(value)?)224 }226 }225227226 fn from_untyped(value: Val) -> Result<Self> {228 fn from_untyped(value: Val) -> Result<Self> {227 <Self as Typed>::TYPE.check(&value)?;229 <Self as Typed>::TYPE.check(&value)?;228 match value {230 match value {229 Val::Num(n) => Ok(n),231 Val::Num(n) => Ok(n.get()),230 _ => unreachable!(),232 _ => unreachable!(),231 }233 }232 }234 }237 const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);239 const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(0.0), None);238240239 fn into_untyped(value: Self) -> Result<Val> {241 fn into_untyped(value: Self) -> Result<Val> {240 Ok(Val::Num(value.0))242 Ok(Val::try_num(value.0)?)241 }243 }242244243 fn from_untyped(value: Val) -> Result<Self> {245 fn from_untyped(value: Val) -> Result<Self> {244 <Self as Typed>::TYPE.check(&value)?;246 <Self as Typed>::TYPE.check(&value)?;245 match value {247 match value {246 Val::Num(n) => Ok(Self(n)),248 Val::Num(n) => Ok(Self(n.get())),247 _ => unreachable!(),249 _ => unreachable!(),248 }250 }249 }251 }253 &ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));255 &ComplexValType::BoundedNumber(Some(0.0), Some(MAX_SAFE_INTEGER));254256255 fn into_untyped(value: Self) -> Result<Val> {257 fn into_untyped(value: Self) -> Result<Val> {256 if value > MAX_SAFE_INTEGER as Self {257 bail!("number is too large")258 }259 Ok(Val::Num(value as f64))258 Ok(Val::try_num(value)?)260 }259 }261260262 fn from_untyped(value: Val) -> Result<Self> {261 fn from_untyped(value: Val) -> Result<Self> {263 <Self as Typed>::TYPE.check(&value)?;262 <Self as Typed>::TYPE.check(&value)?;264 match value {263 match value {265 Val::Num(n) => {264 Val::Num(n) => {265 let n = n.get();266 #[allow(clippy::float_cmp)]266 #[allow(clippy::float_cmp)]267 if n.trunc() != n {267 if n.trunc() != n {268 bail!("cannot convert number with fractional part to usize")268 bail!("cannot convert number with fractional part to usize")479 const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));479 const TYPE: &'static ComplexValType = &ComplexValType::BoundedNumber(Some(-1.0), Some(-1.0));480480481 fn into_untyped(_: Self) -> Result<Val> {481 fn into_untyped(_: Self) -> Result<Val> {482 Ok(Val::Num(-1.0))482 Ok(Val::Num(NumValue::new(-1.0).expect("finite")))483 }483 }484484485 fn from_untyped(value: Val) -> Result<Self> {485 fn from_untyped(value: Val) -> Result<Self> {680 }680 }681}681}682683impl Typed for NumValue {684 const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num);685686 fn into_untyped(typed: Self) -> Result<Val> {687 Ok(Val::Num(typed))688 }689690 fn from_untyped(untyped: Val) -> Result<Self> {691 Self::TYPE.check(&untyped)?;692 match untyped {693 Val::Num(v) => Ok(v),694 _ => unreachable!(),695 }696 }697}682698crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth1use std::{fmt::Display, rc::Rc};1use std::{fmt::Display, rc::Rc};223mod conversions;3pub(crate) mod conversions;4pub use conversions::*;4pub use conversions::*;5use jrsonnet_gcmodule::Trace;5use jrsonnet_gcmodule::Trace;6pub use jrsonnet_types::{ComplexValType, ValType};6pub use jrsonnet_types::{ComplexValType, ValType};155 },155 },156 Self::BoundedNumber(from, to) => {156 Self::BoundedNumber(from, to) => {157 if let Val::Num(n) = value {157 if let Val::Num(n) = value {158 let n = n.get();158 if from.map(|from| from > *n).unwrap_or(false)159 if from.map(|from| from > n).unwrap_or(false)159 || to.map(|to| to < *n).unwrap_or(false)160 || to.map(|to| to < n).unwrap_or(false)160 {161 {161 return Err(TypeError::BoundsFailed(*n, *from, *to).into());162 return Err(TypeError::BoundsFailed(n, *from, *to).into());162 }163 }163 Ok(())164 Ok(())164 } else {165 } else {crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{1use std::{2 cell::RefCell,2 cell::RefCell,3 cmp::Ordering,3 fmt::{self, Debug, Display},4 fmt::{self, Debug, Display},4 mem::replace,5 mem::replace,5 num::NonZeroU32,6 num::NonZeroU32,7 ops::Deref,6 rc::Rc,8 rc::Rc,7};9};81011use derivative::Derivative;9use jrsonnet_gcmodule::{Cc, Trace};12use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::IStr;13use jrsonnet_interner::IStr;11use jrsonnet_types::ValType;14use jrsonnet_types::ValType;15use thiserror::Error;121613pub use crate::arr::{ArrValue, ArrayLike};17pub use crate::arr::{ArrValue, ArrayLike};14use crate::{18use crate::{379}383}380impl Eq for StrValue {}384impl Eq for StrValue {}381impl PartialOrd for StrValue {385impl PartialOrd for StrValue {382 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {386 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {383 Some(self.cmp(other))387 Some(self.cmp(other))384 }388 }385}389}386impl Ord for StrValue {390impl Ord for StrValue {387 fn cmp(&self, other: &Self) -> std::cmp::Ordering {391 fn cmp(&self, other: &Self) -> Ordering {388 let a = self.clone().into_flat();392 let a = self.clone().into_flat();389 let b = other.clone().into_flat();393 let b = other.clone().into_flat();390 a.cmp(&b)394 a.cmp(&b)391 }395 }392}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}393506394/// Represents any valid Jsonnet value.507/// Represents any valid Jsonnet value.395#[derive(Debug, Clone, Trace, Default)]508#[derive(Debug, Clone, Trace, Default)]404 /// Represents a Jsonnet number.517 /// Represents a Jsonnet number.405 /// Should be finite, and not NaN518 /// Should be finite, and not NaN406 /// This restriction isn't enforced by enum, as enum field can't be marked as private519 /// This restriction isn't enforced by enum, as enum field can't be marked as private407 Num(f64),520 Num(NumValue),408 /// Experimental bigint521 /// Experimental bigint409 #[cfg(feature = "exp-bigint")]522 #[cfg(feature = "exp-bigint")]410 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),523 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),449 }562 }450 pub const fn as_num(&self) -> Option<f64> {563 pub const fn as_num(&self) -> Option<f64> {451 match self {564 match self {452 Self::Num(n) => Some(*n),565 Self::Num(n) => Some(n.get()),453 _ => None,566 _ => None,454 }567 }455 }568 }472 }585 }473 }586 }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 }484587485 pub const fn value_type(&self) -> ValType {588 pub const fn value_type(&self) -> ValType {486 match self {589 match self {527 pub fn string(string: impl Into<StrValue>) -> Self {630 pub fn string(string: impl Into<StrValue>) -> Self {528 Self::Str(string.into())631 Self::Str(string.into())529 }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 }530}642}531643532impl From<IStr> for Val {644impl From<IStr> for Val {560 (Val::Bool(a), Val::Bool(b)) => a == b,672 (Val::Bool(a), Val::Bool(b)) => a == b,561 (Val::Null, Val::Null) => true,673 (Val::Null, Val::Null) => true,562 (Val::Str(a), Val::Str(b)) => a == b,674 (Val::Str(a), Val::Str(b)) => a == b,563 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,675 (Val::Num(a), Val::Num(b)) => (a.get() - b.get()).abs() <= f64::EPSILON,564 #[cfg(feature = "exp-bigint")]676 #[cfg(feature = "exp-bigint")]565 (Val::BigInt(a), Val::BigInt(b)) => a == b,677 (Val::BigInt(a), Val::BigInt(b)) => a == b,566 (Val::Arr(_), Val::Arr(_)) => {678 (Val::Arr(_), Val::Arr(_)) => {crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth275 if arr.is_empty() {275 if arr.is_empty() {276 return eval_on_empty(onEmpty);276 return eval_on_empty(onEmpty);277 }277 }278 Ok(Val::Num(arr.iter().sum::<f64>() / (arr.len() as f64)))278 Ok(Val::try_num(arr.iter().sum::<f64>() / (arr.len() as f64))?)279}279}280280281#[builtin]281#[builtin]crates/jrsonnet-stdlib/src/operator.rsdiffbeforeafterboth6 operator::evaluate_mod_op,6 operator::evaluate_mod_op,7 stdlib::std_format,7 stdlib::std_format,8 typed::{Either, Either2},8 typed::{Either, Either2},9 val::{equals, primitive_equals},9 val::{equals, primitive_equals, NumValue},10 IStr, Result, Val,10 IStr, Result, Val,11};11};121213#[builtin]13#[builtin]14pub fn builtin_mod(a: Either![f64, IStr], b: Val) -> Result<Val> {14pub fn builtin_mod(a: Either![NumValue, IStr], b: Val) -> Result<Val> {15 use Either2::*;15 use Either2::*;16 evaluate_mod_op(16 evaluate_mod_op(17 &match a {17 &match a {crates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth20 Unknown,20 Unknown,21}21}2223#[derive(PartialEq)]24struct NonNaNf64(f64);25impl PartialOrd for NonNaNf64 {26 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {27 Some(self.cmp(other))28 }29}30impl Eq for NonNaNf64 {}31impl Ord for NonNaNf64 {32 fn cmp(&self, other: &Self) -> std::cmp::Ordering {33 self.0.partial_cmp(&other.0).expect("non nan")34 }35}362237fn get_sort_type<T>(values: &[T], key_getter: impl Fn(&T) -> &Val) -> Result<SortKeyType> {23fn get_sort_type<T>(values: &[T], key_getter: impl Fn(&T) -> &Val) -> Result<SortKeyType> {38 let mut sort_type = SortKeyType::Unknown;24 let mut sort_type = SortKeyType::Unknown;56 let sort_type = get_sort_type(&values, |k| k)?;42 let sort_type = get_sort_type(&values, |k| k)?;57 match sort_type {43 match sort_type {58 SortKeyType::Number => values.sort_unstable_by_key(|v| match v {44 SortKeyType::Number => values.sort_unstable_by_key(|v| match v {59 Val::Num(n) => NonNaNf64(*n),45 Val::Num(n) => *n,60 _ => unreachable!(),46 _ => unreachable!(),61 }),47 }),62 SortKeyType::String => values.sort_unstable_by_key(|v| match v {48 SortKeyType::String => values.sort_unstable_by_key(|v| match v {95 let sort_type = get_sort_type(&vk, |v| &v.1)?;81 let sort_type = get_sort_type(&vk, |v| &v.1)?;96 match sort_type {82 match sort_type {97 SortKeyType::Number => vk.sort_by_key(|v| match v.1 {83 SortKeyType::Number => vk.sort_by_key(|v| match v.1 {98 Val::Num(n) => NonNaNf64(n),84 Val::Num(n) => n,99 _ => unreachable!(),85 _ => unreachable!(),100 }),86 }),101 SortKeyType::String => vk.sort_by_key(|v| match &v.1 {87 SortKeyType::String => vk.sort_by_key(|v| match &v.1 {crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth116 .enumerate()116 .enumerate()117 {117 {118 if &strb[i..i + pat.len()] == pat {118 if &strb[i..i + pat.len()] == pat {119 out.push(Val::Num(ch_idx as f64));119 out.push(Val::Num(120 ch_idx.try_into().expect("unrealisticly long string"),121 ));120 }122 }121 }123 }