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.rsdiffbeforeafterboth1use std::{2 cmp::Ordering,3 fmt::{Debug, Display},4 path::PathBuf,5};67use jrsonnet_gcmodule::Trace;8use jrsonnet_interner::IStr;9use jrsonnet_parser::{BinaryOpType, ExprLocation, LocExpr, Source, SourcePath, UnaryOpType};10use jrsonnet_types::ValType;11use thiserror::Error;1213use crate::{14 function::{builtin::ParamDefault, CallLocation},15 stdlib::format::FormatError,16 typed::TypeLocError,17 ObjValue,18};1920pub(crate) fn format_found(list: &[IStr], what: &str) -> String {21 if list.is_empty() {22 return String::new();23 }24 let mut out = String::new();25 out.push_str("\nThere is ");26 out.push_str(what);27 if list.len() > 1 {28 out.push('s');29 }30 out.push_str(" with similar name");31 if list.len() > 1 {32 out.push('s');33 }34 out.push_str(" present: ");35 for (i, v) in list.iter().enumerate() {36 if i != 0 {37 out.push_str(", ");38 }39 out.push_str(v as &str);40 }41 out42}4344fn format_signature(sig: &FunctionSignature) -> String {45 let mut out = String::new();46 out.push_str("\nFunction has the following signature: ");47 out.push('(');48 if sig.is_empty() {49 out.push_str("/*no arguments*/");50 } else {51 for (i, (name, default)) in sig.iter().enumerate() {52 if i != 0 {53 out.push_str(", ");54 }55 if let Some(name) = name {56 out.push_str(name);57 } else {58 out.push_str("<unnamed>");59 }60 match default {61 ParamDefault::None => {}62 ParamDefault::Exists => out.push_str(" = <default>"),63 ParamDefault::Literal(lit) => {64 out.push_str(" = ");65 out.push_str(lit);66 }67 }68 }69 }70 out.push(')');71 out72}7374const fn format_empty_str(str: &str) -> &str {75 if str.is_empty() {76 "\"\" (empty string)"77 } else {78 str79 }80}8182pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {83 let mut heap = Vec::new();84 for field in v.fields_ex(85 true,86 #[cfg(feature = "exp-preserve-order")]87 false,88 ) {89 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());90 if conf < 0.8 {91 continue;92 }93 assert!(field.as_str() != key.as_str(), "looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");9495 heap.push((conf, field));96 }97 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));98 heap.into_iter().map(|v| v.1).collect()99}100101type FunctionSignature = Vec<(Option<IStr>, ParamDefault)>;102103/// Possible errors104#[allow(missing_docs)]105#[derive(Error, Debug, Clone, Trace)]106#[non_exhaustive]107pub enum ErrorKind {108 #[error("intrinsic not found: {0}")]109 IntrinsicNotFound(IStr),110111 #[error("operator {0} does not operate on type {1}")]112 UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),113 #[error("binary operation {1} {0} {2} is not implemented")]114 BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),115116 #[error("no top level object in this context")]117 NoTopLevelObjectFound,118 #[error("self is only usable inside objects")]119 CantUseSelfOutsideOfObject,120 #[error("no super found")]121 NoSuperFound,122123 #[error("for loop can only iterate over arrays")]124 InComprehensionCanOnlyIterateOverArray,125126 #[error("array out of bounds: {0} is not within [0,{1})")]127 ArrayBoundsError(isize, usize),128 #[error("string out of bounds: {0} is not within [0,{1})")]129 StringBoundsError(usize, usize),130131 #[error("assert failed: {}", format_empty_str(.0))]132 AssertionFailed(IStr),133134 #[error("variable is not defined: {0}{}", format_found(.1, "variable"))]135 VariableIsNotDefined(IStr, Vec<IStr>),136 #[error("duplicate local var: {0}")]137 DuplicateLocalVar(IStr),138139 #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]140 TypeMismatch(&'static str, Vec<ValType>, ValType),141 #[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]142 NoSuchField(IStr, Vec<IStr>),143144 #[error("only functions can be called, got {0}")]145 OnlyFunctionsCanBeCalledGot(ValType),146 #[error("parameter {0} is not defined")]147 UnknownFunctionParameter(String),148 #[error("argument {0} is already bound")]149 BindingParameterASecondTime(IStr),150 #[error("too many args, function has {0}{}", format_signature(.1))]151 TooManyArgsFunctionHas(usize, FunctionSignature),152 #[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]153 FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),154155 #[error("external variable is not defined: {0}")]156 UndefinedExternalVariable(IStr),157158 #[error("field name should be string, got {0}")]159 FieldMustBeStringGot(ValType),160 #[error("duplicate field name: {}", format_empty_str(.0))]161 DuplicateFieldName(IStr),162163 #[error("attempted to index array with string {}", format_empty_str(.0))]164 AttemptedIndexAnArrayWithString(IStr),165 #[error("{0} index type should be {1}, got {2}")]166 ValueIndexMustBeTypeGot(ValType, ValType, ValType),167 #[error("cant index into {0}")]168 CantIndexInto(ValType),169 #[error("{0} is not indexable")]170 ValueIsNotIndexable(ValType),171172 #[error("super can't be used standalone")]173 StandaloneSuper,174175 #[error("can't resolve {1} from {0}")]176 ImportFileNotFound(SourcePath, String),177 #[error("can't resolve absolute {0}")]178 AbsoluteImportFileNotFound(PathBuf),179 #[error("resolved file not found: {:?}", .0)]180 ResolvedFileNotFound(SourcePath),181 #[error("can't import {0}: is a directory")]182 ImportIsADirectory(SourcePath),183 #[error("imported file is not valid utf-8: {0:?}")]184 ImportBadFileUtf8(SourcePath),185 #[error("import io error: {0}")]186 ImportIo(String),187 #[error("tried to import {1} from {0}, but imports are not supported")]188 ImportNotSupported(SourcePath, String),189 #[error("tried to import {0}, but absolute imports are not supported")]190 AbsoluteImportNotSupported(PathBuf),191 #[error("can't import from virtual file")]192 CantImportFromVirtualFile,193 #[error(194 "syntax error: {}",195 // Peg has no fancier way to handle critical parsing errors https://github.com/kevinmehall/rust-peg/issues/225196 {.error.expected.tokens().find(|t| t.starts_with("!!!")).map_or_else(|| {197 format!(198 "expected {}, got {:?}",199 .error.expected,200 .path.code().chars().nth(error.location.offset)201 .map_or_else(|| "EOF".into(), |c| c.to_string())202 )203 }, |v| v[3..].into())}204 )]205 ImportSyntaxError {206 path: Source,207 #[trace(skip)]208 error: Box<jrsonnet_parser::ParseError>,209 },210211 #[error("runtime error: {}", format_empty_str(.0))]212 RuntimeError(IStr),213 #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]214 StackOverflow,215 #[error("infinite recursion detected")]216 InfiniteRecursionDetected,217 #[error("tried to index by fractional value")]218 FractionalIndex,219 #[error("attempted to divide by zero")]220 DivisionByZero,221222 #[error("string manifest output is not an string")]223 StringManifestOutputIsNotAString,224 #[error("stream manifest output is not an array")]225 StreamManifestOutputIsNotAArray,226 #[error("multi manifest output is not an object")]227 MultiManifestOutputIsNotAObject,228229 #[error("cant recurse stream manifest")]230 StreamManifestOutputCannotBeRecursed,231 #[error("stream manifest output cannot consist of raw strings")]232 StreamManifestCannotNestString,233234 #[error("{}", format_empty_str(.0))]235 ImportCallbackError(String),236 #[error("invalid unicode codepoint: {0}")]237 InvalidUnicodeCodepointGot(u32),238239 #[error("format error: {0}")]240 Format(#[from] FormatError),241 #[error("type error: {0}")]242 TypeError(TypeLocError),243244 #[cfg(feature = "anyhow-error")]245 #[error(transparent)]246 Other(#[trace(skip)] std::rc::Rc<anyhow::Error>),247}248249#[cfg(feature = "anyhow-error")]250impl From<anyhow::Error> for Error {251 fn from(e: anyhow::Error) -> Self {252 Self::new(ErrorKind::Other(std::rc::Rc::new(e)))253 }254}255256impl From<ErrorKind> for Error {257 fn from(e: ErrorKind) -> Self {258 Self::new(e)259 }260}261262/// Single stack trace frame263#[derive(Clone, Debug, Trace)]264pub struct StackTraceElement {265 /// Source of this frame266 /// Some frames only act as description, without attached source267 pub location: Option<ExprLocation>,268 /// Frame description269 pub desc: String,270}271#[derive(Debug, Clone, Trace)]272pub struct StackTrace(pub Vec<StackTraceElement>);273274#[derive(Clone, Trace)]275pub struct Error(Box<(ErrorKind, StackTrace)>);276impl Error {277 pub fn new(e: ErrorKind) -> Self {278 Self(Box::new((e, StackTrace(vec![]))))279 }280281 pub const fn error(&self) -> &ErrorKind {282 &(self.0).0283 }284 pub fn error_mut(&mut self) -> &mut ErrorKind {285 &mut (self.0).0286 }287 pub const fn trace(&self) -> &StackTrace {288 &(self.0).1289 }290 pub fn trace_mut(&mut self) -> &mut StackTrace {291 &mut (self.0).1292 }293}294impl Display for Error {295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {296 writeln!(f, "{}", self.0 .0)?;297 for el in &self.0 .1 .0 {298 write!(f, "\t{}", el.desc)?;299 if let Some(loc) = &el.location {300 write!(f, "at {}", loc.0 .0 .0)?;301 loc.0.map_source_locations(&[loc.1, loc.2]);302 }303 writeln!(f)?;304 }305 Ok(())306 }307}308impl Debug for Error {309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {310 f.debug_tuple("LocError").field(&self.0).finish()311 }312}313impl std::error::Error for Error {}314315pub trait ErrorSource {316 fn to_location(self) -> Option<ExprLocation>;317}318impl ErrorSource for &LocExpr {319 fn to_location(self) -> Option<ExprLocation> {320 Some(self.1.clone())321 }322}323impl ErrorSource for &ExprLocation {324 fn to_location(self) -> Option<ExprLocation> {325 Some(self.clone())326 }327}328impl ErrorSource for CallLocation<'_> {329 fn to_location(self) -> Option<ExprLocation> {330 self.0.cloned()331 }332}333334pub type Result<V, E = Error> = std::result::Result<V, E>;335pub trait ResultExt: Sized {336 #[must_use]337 fn with_description<O: Into<String>>(self, msg: impl FnOnce() -> O) -> Self;338 #[must_use]339 fn description(self, msg: &str) -> Self {340 self.with_description(|| msg)341 }342343 #[must_use]344 fn with_description_src<O: Into<String>>(345 self,346 src: impl ErrorSource,347 msg: impl FnOnce() -> O,348 ) -> Self;349 #[must_use]350 fn description_src(self, src: impl ErrorSource, msg: &str) -> Self {351 self.with_description_src(src, || msg)352 }353}354impl<T> ResultExt for Result<T, Error> {355 fn with_description<O: Into<String>>(mut self, msg: impl FnOnce() -> O) -> Self {356 if let Err(e) = &mut self {357 let trace = e.trace_mut();358 trace.0.push(StackTraceElement {359 location: None,360 desc: msg().into(),361 });362 }363 self364 }365366 fn with_description_src<O: Into<String>>(367 mut self,368 src: impl ErrorSource,369 msg: impl FnOnce() -> O,370 ) -> Self {371 if let Err(e) = &mut self {372 let trace = e.trace_mut();373 trace.0.push(StackTraceElement {374 location: src.to_location(),375 desc: msg().into(),376 });377 }378 self379 }380}381382#[macro_export]383macro_rules! bail {384 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {385 return Err($w$(::$i)*$(($($tt)*))?.into())386 };387 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {388 return Err($w$(::$i)*$({$($tt)*})?.into())389 };390 ($l:literal$(, $($tt:tt)*)?) => {391 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())392 };393}394395#[macro_export]396macro_rules! runtime_error {397 ($l:literal$(, $($tt:tt)*)?) => {398 $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))399 };400}1use std::{2 cmp::Ordering, convert::Infallible, fmt::{Debug, Display}, path::PathBuf3};45use jrsonnet_gcmodule::Trace;6use jrsonnet_interner::IStr;7use jrsonnet_parser::{BinaryOpType, ExprLocation, LocExpr, Source, SourcePath, UnaryOpType};8use jrsonnet_types::ValType;9use thiserror::Error;1011use crate::{12 function::{builtin::ParamDefault, CallLocation},13 stdlib::format::FormatError,14 typed::TypeLocError,15 val::ConvertNumValueError,16 ObjValue,17};1819pub(crate) fn format_found(list: &[IStr], what: &str) -> String {20 if list.is_empty() {21 return String::new();22 }23 let mut out = String::new();24 out.push_str("\nThere is ");25 out.push_str(what);26 if list.len() > 1 {27 out.push('s');28 }29 out.push_str(" with similar name");30 if list.len() > 1 {31 out.push('s');32 }33 out.push_str(" present: ");34 for (i, v) in list.iter().enumerate() {35 if i != 0 {36 out.push_str(", ");37 }38 out.push_str(v as &str);39 }40 out41}4243fn format_signature(sig: &FunctionSignature) -> String {44 let mut out = String::new();45 out.push_str("\nFunction has the following signature: ");46 out.push('(');47 if sig.is_empty() {48 out.push_str("/*no arguments*/");49 } else {50 for (i, (name, default)) in sig.iter().enumerate() {51 if i != 0 {52 out.push_str(", ");53 }54 if let Some(name) = name {55 out.push_str(name);56 } else {57 out.push_str("<unnamed>");58 }59 match default {60 ParamDefault::None => {}61 ParamDefault::Exists => out.push_str(" = <default>"),62 ParamDefault::Literal(lit) => {63 out.push_str(" = ");64 out.push_str(lit);65 }66 }67 }68 }69 out.push(')');70 out71}7273const fn format_empty_str(str: &str) -> &str {74 if str.is_empty() {75 "\"\" (empty string)"76 } else {77 str78 }79}8081pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {82 let mut heap = Vec::new();83 for field in v.fields_ex(84 true,85 #[cfg(feature = "exp-preserve-order")]86 false,87 ) {88 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());89 if conf < 0.8 {90 continue;91 }92 assert!(field.as_str() != key.as_str(), "looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!");9394 heap.push((conf, field));95 }96 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));97 heap.into_iter().map(|v| v.1).collect()98}99100type FunctionSignature = Vec<(Option<IStr>, ParamDefault)>;101102/// Possible errors103#[allow(missing_docs)]104#[derive(Error, Debug, Clone, Trace)]105#[non_exhaustive]106pub enum ErrorKind {107 #[error("intrinsic not found: {0}")]108 IntrinsicNotFound(IStr),109110 #[error("operator {0} does not operate on type {1}")]111 UnaryOperatorDoesNotOperateOnType(UnaryOpType, ValType),112 #[error("binary operation {1} {0} {2} is not implemented")]113 BinaryOperatorDoesNotOperateOnValues(BinaryOpType, ValType, ValType),114115 #[error("no top level object in this context")]116 NoTopLevelObjectFound,117 #[error("self is only usable inside objects")]118 CantUseSelfOutsideOfObject,119 #[error("no super found")]120 NoSuperFound,121122 #[error("for loop can only iterate over arrays")]123 InComprehensionCanOnlyIterateOverArray,124125 #[error("array out of bounds: {0} is not within [0,{1})")]126 ArrayBoundsError(isize, usize),127 #[error("string out of bounds: {0} is not within [0,{1})")]128 StringBoundsError(usize, usize),129130 #[error("assert failed: {}", format_empty_str(.0))]131 AssertionFailed(IStr),132133 #[error("variable is not defined: {0}{}", format_found(.1, "variable"))]134 VariableIsNotDefined(IStr, Vec<IStr>),135 #[error("duplicate local var: {0}")]136 DuplicateLocalVar(IStr),137138 #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]139 TypeMismatch(&'static str, Vec<ValType>, ValType),140 #[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]141 NoSuchField(IStr, Vec<IStr>),142143 #[error("only functions can be called, got {0}")]144 OnlyFunctionsCanBeCalledGot(ValType),145 #[error("parameter {0} is not defined")]146 UnknownFunctionParameter(String),147 #[error("argument {0} is already bound")]148 BindingParameterASecondTime(IStr),149 #[error("too many args, function has {0}{}", format_signature(.1))]150 TooManyArgsFunctionHas(usize, FunctionSignature),151 #[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]152 FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),153154 #[error("external variable is not defined: {0}")]155 UndefinedExternalVariable(IStr),156157 #[error("field name should be string, got {0}")]158 FieldMustBeStringGot(ValType),159 #[error("duplicate field name: {}", format_empty_str(.0))]160 DuplicateFieldName(IStr),161162 #[error("attempted to index array with string {}", format_empty_str(.0))]163 AttemptedIndexAnArrayWithString(IStr),164 #[error("{0} index type should be {1}, got {2}")]165 ValueIndexMustBeTypeGot(ValType, ValType, ValType),166 #[error("cant index into {0}")]167 CantIndexInto(ValType),168 #[error("{0} is not indexable")]169 ValueIsNotIndexable(ValType),170171 #[error("super can't be used standalone")]172 StandaloneSuper,173174 #[error("can't resolve {1} from {0}")]175 ImportFileNotFound(SourcePath, String),176 #[error("can't resolve absolute {0}")]177 AbsoluteImportFileNotFound(PathBuf),178 #[error("resolved file not found: {:?}", .0)]179 ResolvedFileNotFound(SourcePath),180 #[error("can't import {0}: is a directory")]181 ImportIsADirectory(SourcePath),182 #[error("imported file is not valid utf-8: {0:?}")]183 ImportBadFileUtf8(SourcePath),184 #[error("import io error: {0}")]185 ImportIo(String),186 #[error("tried to import {1} from {0}, but imports are not supported")]187 ImportNotSupported(SourcePath, String),188 #[error("tried to import {0}, but absolute imports are not supported")]189 AbsoluteImportNotSupported(PathBuf),190 #[error("can't import from virtual file")]191 CantImportFromVirtualFile,192 #[error(193 "syntax error: {}",194 // Peg has no fancier way to handle critical parsing errors https://github.com/kevinmehall/rust-peg/issues/225195 {.error.expected.tokens().find(|t| t.starts_with("!!!")).map_or_else(|| {196 format!(197 "expected {}, got {:?}",198 .error.expected,199 .path.code().chars().nth(error.location.offset)200 .map_or_else(|| "EOF".into(), |c| c.to_string())201 )202 }, |v| v[3..].into())}203 )]204 ImportSyntaxError {205 path: Source,206 #[trace(skip)]207 error: Box<jrsonnet_parser::ParseError>,208 },209210 #[error("runtime error: {}", format_empty_str(.0))]211 RuntimeError(IStr),212 #[error("stack overflow, try to reduce recursion, or set --max-stack to bigger value")]213 StackOverflow,214 #[error("infinite recursion detected")]215 InfiniteRecursionDetected,216 #[error("tried to index by fractional value")]217 FractionalIndex,218 #[error("attempted to divide by zero")]219 DivisionByZero,220221 #[error("string manifest output is not an string")]222 StringManifestOutputIsNotAString,223 #[error("stream manifest output is not an array")]224 StreamManifestOutputIsNotAArray,225 #[error("multi manifest output is not an object")]226 MultiManifestOutputIsNotAObject,227228 #[error("cant recurse stream manifest")]229 StreamManifestOutputCannotBeRecursed,230 #[error("stream manifest output cannot consist of raw strings")]231 StreamManifestCannotNestString,232233 #[error("{}", format_empty_str(.0))]234 ImportCallbackError(String),235 #[error("invalid unicode codepoint: {0}")]236 InvalidUnicodeCodepointGot(u32),237238 #[error("convert num value: {0}")]239 ConvertNumValue(#[from] ConvertNumValueError),240241 #[error("format error: {0}")]242 Format(#[from] FormatError),243 #[error("type error: {0}")]244 TypeError(TypeLocError),245246 #[cfg(feature = "anyhow-error")]247 #[error(transparent)]248 Other(#[trace(skip)] std::rc::Rc<anyhow::Error>),249}250251#[cfg(feature = "anyhow-error")]252impl From<anyhow::Error> for Error {253 fn from(e: anyhow::Error) -> Self {254 Self::new(ErrorKind::Other(std::rc::Rc::new(e)))255 }256}257258impl From<ErrorKind> for Error {259 fn from(e: ErrorKind) -> Self {260 Self::new(e)261 }262}263264impl From<Infallible> for Error {265 fn from(_value: Infallible) -> Self {266 unreachable!()267 }268}269270/// Single stack trace frame271#[derive(Clone, Debug, Trace)]272pub struct StackTraceElement {273 /// Source of this frame274 /// Some frames only act as description, without attached source275 pub location: Option<ExprLocation>,276 /// Frame description277 pub desc: String,278}279#[derive(Debug, Clone, Trace)]280pub struct StackTrace(pub Vec<StackTraceElement>);281282#[derive(Clone, Trace)]283pub struct Error(Box<(ErrorKind, StackTrace)>);284impl Error {285 pub fn new(e: ErrorKind) -> Self {286 Self(Box::new((e, StackTrace(vec![]))))287 }288289 pub const fn error(&self) -> &ErrorKind {290 &(self.0).0291 }292 pub fn error_mut(&mut self) -> &mut ErrorKind {293 &mut (self.0).0294 }295 pub const fn trace(&self) -> &StackTrace {296 &(self.0).1297 }298 pub fn trace_mut(&mut self) -> &mut StackTrace {299 &mut (self.0).1300 }301}302impl Display for Error {303 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {304 writeln!(f, "{}", self.0 .0)?;305 for el in &self.0 .1 .0 {306 write!(f, "\t{}", el.desc)?;307 if let Some(loc) = &el.location {308 write!(f, "at {}", loc.0 .0 .0)?;309 loc.0.map_source_locations(&[loc.1, loc.2]);310 }311 writeln!(f)?;312 }313 Ok(())314 }315}316impl Debug for Error {317 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {318 f.debug_tuple("LocError").field(&self.0).finish()319 }320}321impl std::error::Error for Error {}322323pub trait ErrorSource {324 fn to_location(self) -> Option<ExprLocation>;325}326impl ErrorSource for &LocExpr {327 fn to_location(self) -> Option<ExprLocation> {328 Some(self.1.clone())329 }330}331impl ErrorSource for &ExprLocation {332 fn to_location(self) -> Option<ExprLocation> {333 Some(self.clone())334 }335}336impl ErrorSource for CallLocation<'_> {337 fn to_location(self) -> Option<ExprLocation> {338 self.0.cloned()339 }340}341342pub type Result<V, E = Error> = std::result::Result<V, E>;343pub trait ResultExt: Sized {344 #[must_use]345 fn with_description<O: Into<String>>(self, msg: impl FnOnce() -> O) -> Self;346 #[must_use]347 fn description(self, msg: &str) -> Self {348 self.with_description(|| msg)349 }350351 #[must_use]352 fn with_description_src<O: Into<String>>(353 self,354 src: impl ErrorSource,355 msg: impl FnOnce() -> O,356 ) -> Self;357 #[must_use]358 fn description_src(self, src: impl ErrorSource, msg: &str) -> Self {359 self.with_description_src(src, || msg)360 }361}362impl<T> ResultExt for Result<T, Error> {363 fn with_description<O: Into<String>>(mut self, msg: impl FnOnce() -> O) -> Self {364 if let Err(e) = &mut self {365 let trace = e.trace_mut();366 trace.0.push(StackTraceElement {367 location: None,368 desc: msg().into(),369 });370 }371 self372 }373374 fn with_description_src<O: Into<String>>(375 mut self,376 src: impl ErrorSource,377 msg: impl FnOnce() -> O,378 ) -> Self {379 if let Err(e) = &mut self {380 let trace = e.trace_mut();381 trace.0.push(StackTraceElement {382 location: src.to_location(),383 desc: msg().into(),384 });385 }386 self387 }388}389390#[macro_export]391macro_rules! bail {392 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {393 return Err($w$(::$i)*$(($($tt)*))?.into())394 };395 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {396 return Err($w$(::$i)*$({$($tt)*})?.into())397 };398 ($l:literal$(, $($tt:tt)*)?) => {399 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())400 };401}402403#[macro_export]404macro_rules! runtime_error {405 ($l:literal$(, $($tt:tt)*)?) => {406 $crate::error::Error::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)))407 };408}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.rsdiffbeforeafterboth--- 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<std::cmp::Ordering> {
+ fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
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<Self> {
+ 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<Ordering> {
+ 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<ConvertNumValueError> 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<Self, ConvertNumValueError> {
+ 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<f64> for NumValue {
+ type Error = ConvertNumValueError;
+
+ fn try_from(value: f64) -> Result<Self, Self::Error> {
+ Self::new(value).ok_or(ConvertNumValueError::NonFinite)
+ }
+}
+impl TryFrom<f32> for NumValue {
+ type Error = ConvertNumValueError;
+
+ fn try_from(value: f32) -> Result<Self, Self::Error> {
+ 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<num_bigint::BigInt>),
@@ -449,7 +562,7 @@
}
pub const fn as_num(&self) -> Option<f64> {
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<Self> {
- 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<StrValue>) -> Self {
Self::Str(string.into())
}
+ pub fn num(num: impl Into<NumValue>) -> Self {
+ Self::Num(num.into())
+ }
+ pub fn try_num<V, E>(num: V) -> Result<Self, E>
+ where
+ NumValue: TryFrom<V, Error = E>,
+ {
+ Ok(Self::Num(num.try_into()?))
+ }
}
impl From<IStr> 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(_)) => {
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()