diff --git a/crates/nu-data/src/base.rs b/crates/nu-data/src/base.rs index bad3901718..0f020ed3fc 100644 --- a/crates/nu-data/src/base.rs +++ b/crates/nu-data/src/base.rs @@ -77,7 +77,7 @@ pub enum CompareValues { Decimals(BigDecimal, BigDecimal), String(String, String), Date(DateTime, DateTime), - DateDuration(DateTime, i64), + DateDuration(DateTime, BigInt), Booleans(bool, bool), } @@ -92,9 +92,11 @@ impl CompareValues { CompareValues::Date(left, right) => left.cmp(right), CompareValues::DateDuration(left, right) => { // FIXME: Not sure if I could do something better with the Span. - let duration = - Primitive::into_chrono_duration(Primitive::Duration(*right), Span::unknown()) - .expect("Could not convert nushell Duration into chrono Duration."); + let duration = Primitive::into_chrono_duration( + Primitive::Duration(right.clone()), + Span::unknown(), + ) + .expect("Could not convert nushell Duration into chrono Duration."); let right: DateTime = Utc::now() .checked_sub_signed(duration) .expect("Data overflow") @@ -158,7 +160,7 @@ pub fn coerce_compare_primitive( (Nothing, Nothing) => CompareValues::Booleans(true, true), (String(left), String(right)) => CompareValues::String(left.clone(), right.clone()), (Date(left), Date(right)) => CompareValues::Date(*left, *right), - (Date(left), Duration(right)) => CompareValues::DateDuration(*left, *right), + (Date(left), Duration(right)) => CompareValues::DateDuration(*left, right.clone()), (Boolean(left), Boolean(right)) => CompareValues::Booleans(*left, *right), (Boolean(left), Nothing) => CompareValues::Booleans(*left, false), (Nothing, Boolean(right)) => CompareValues::Booleans(false, *right), diff --git a/crates/nu-data/src/base/shape.rs b/crates/nu-data/src/base/shape.rs index a07dd3c450..d6c1051649 100644 --- a/crates/nu-data/src/base/shape.rs +++ b/crates/nu-data/src/base/shape.rs @@ -33,7 +33,7 @@ pub enum InlineShape { GlobPattern(String), Boolean(bool), Date(DateTime), - Duration(i64), + Duration(BigInt), FilePath(PathBuf), Binary(usize), @@ -94,7 +94,7 @@ impl InlineShape { Primitive::GlobPattern(pattern) => InlineShape::GlobPattern(pattern.clone()), Primitive::Boolean(boolean) => InlineShape::Boolean(*boolean), Primitive::Date(date) => InlineShape::Date(*date), - Primitive::Duration(duration) => InlineShape::Duration(*duration), + Primitive::Duration(duration) => InlineShape::Duration(duration.clone()), Primitive::FilePath(path) => InlineShape::FilePath(path.clone()), Primitive::Binary(b) => InlineShape::Binary(b.len()), Primitive::BeginningOfStream => InlineShape::BeginningOfStream, @@ -304,9 +304,10 @@ impl PrettyDebug for FormatInlineShape { .to_owned(), ), InlineShape::Date(date) => DbgDocBldr::primitive(nu_protocol::format_date(date)), - InlineShape::Duration(duration) => { - DbgDocBldr::description(format_primitive(&Primitive::Duration(*duration), None)) - } + InlineShape::Duration(duration) => DbgDocBldr::description(format_primitive( + &Primitive::Duration(duration.clone()), + None, + )), InlineShape::FilePath(path) => DbgDocBldr::primitive(path.display()), InlineShape::Binary(length) => { DbgDocBldr::opaque(format!("", length)) diff --git a/crates/nu-engine/src/from_value.rs b/crates/nu-engine/src/from_value.rs index fb0fd440ef..9106518a2b 100644 --- a/crates/nu-engine/src/from_value.rs +++ b/crates/nu-engine/src/from_value.rs @@ -35,7 +35,7 @@ impl FromValue for Tagged { Value { value: UntaggedValue::Primitive(Primitive::Duration(i)), .. - } => Ok(BigInt::from(*i).tagged(tag)), + } => Ok(i.clone().tagged(tag)), Value { tag, .. } => Err(ShellError::labeled_error( "Can't convert to integer", "can't convert to integer", @@ -59,7 +59,7 @@ impl FromValue for num_bigint::BigInt { Value { value: UntaggedValue::Primitive(Primitive::Duration(i)), .. - } => Ok(BigInt::from(*i)), + } => Ok(i.clone()), Value { tag, .. } => Err(ShellError::labeled_error( "Can't convert to integer", "can't convert to integer", diff --git a/crates/nu-protocol/src/hir.rs b/crates/nu-protocol/src/hir.rs index dadf9a9bd4..da5f6242d3 100644 --- a/crates/nu-protocol/src/hir.rs +++ b/crates/nu-protocol/src/hir.rs @@ -646,8 +646,8 @@ pub fn filesize(size_in_bytes: Number) -> UntaggedValue { } } -pub fn duration(nanos: i64) -> UntaggedValue { - UntaggedValue::Primitive(Primitive::Duration(nanos)) +pub fn duration(nanos: impl Into) -> UntaggedValue { + UntaggedValue::Primitive(Primitive::Duration(nanos.into())) } #[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Deserialize, Serialize)] diff --git a/crates/nu-protocol/src/value.rs b/crates/nu-protocol/src/value.rs index bff7be1416..f6cf0fcd7e 100644 --- a/crates/nu-protocol/src/value.rs +++ b/crates/nu-protocol/src/value.rs @@ -250,8 +250,8 @@ impl UntaggedValue { } /// Helper for creating date duration values - pub fn duration(nanos: i64) -> UntaggedValue { - UntaggedValue::Primitive(Primitive::Duration(nanos)) + pub fn duration(nanos: impl Into) -> UntaggedValue { + UntaggedValue::Primitive(Primitive::Duration(nanos.into())) } /// Helper for creating datatime values @@ -369,10 +369,10 @@ impl Value { } } - /// View the Value as a Duration (i64), if possible - pub fn as_duration(&self) -> Result { + /// View the Value as a Duration (BigInt), if possible + pub fn as_duration(&self) -> Result { match &self.value { - UntaggedValue::Primitive(Primitive::Duration(dur)) => Ok(*dur), + UntaggedValue::Primitive(Primitive::Duration(dur)) => Ok(dur.clone()), _ => Err(ShellError::type_error("bigint", self.spanned_type_name())), } } @@ -836,7 +836,10 @@ impl I64Ext for i64 { fn to_duration_value(&self, the_tag: Tag) -> Value { Value { - value: UntaggedValue::Primitive(Primitive::Duration(*self)), + value: UntaggedValue::Primitive(Primitive::Duration( + BigInt::from_i64(*self) + .expect("Internal error: conversion to big int should not fail"), + )), tag: the_tag, } } diff --git a/crates/nu-protocol/src/value/primitive.rs b/crates/nu-protocol/src/value/primitive.rs index 0228071d13..e0af80d838 100644 --- a/crates/nu-protocol/src/value/primitive.rs +++ b/crates/nu-protocol/src/value/primitive.rs @@ -45,7 +45,7 @@ pub enum Primitive { /// A date value Date(DateTime), /// A count in the number of nanoseconds - Duration(i64), + Duration(BigInt), /// A range of values Range(Box), /// A file path @@ -171,7 +171,13 @@ impl Primitive { "converting a decimal into a signed 64-bit integer", ) }), - Primitive::Duration(duration) => Ok(*duration), + Primitive::Duration(duration) => duration.to_i64().ok_or_else(|| { + ShellError::range_error( + ExpectedRange::I64, + &format!("{}", duration).spanned(span), + "converting a duration into a signed 64-bit integer", + ) + }), other => Err(ShellError::type_error( "number", other.type_name().spanned(span), @@ -277,7 +283,10 @@ impl Primitive { match self { Primitive::Duration(duration) => { // Divide into seconds because BigInt can be larger than i64 - let (secs, nanos) = duration.div_rem(&(NANOS_PER_SEC as i64)); + let (secs, nanos) = duration.div_rem( + &BigInt::from_u32(NANOS_PER_SEC) + .expect("Internal error: conversion from u32 failed"), + ); let secs = match secs.to_i64() { Some(secs) => secs, None => { @@ -396,7 +405,10 @@ impl From for Primitive { .expect("Unexpected overflow") .num_nanoseconds() .expect("Unexpected overflow") as u32; - Primitive::Duration(secs * NANOS_PER_SEC as i64 + nanos as i64) + Primitive::Duration( + BigInt::from_i64(secs * NANOS_PER_SEC as i64 + nanos as i64) + .expect("Internal error: can't convert from i64"), + ) } } @@ -513,20 +525,24 @@ pub fn format_primitive(primitive: &Primitive, field_name: Option<&String>) -> S } /// Format a duration in nanoseconds into a string -pub fn format_duration(duration: &i64) -> String { +pub fn format_duration(duration: &BigInt) -> String { let is_zero = duration.is_zero(); + // FIXME: This involves a lot of allocation, but it seems inevitable with BigInt. + let big_int_1000 = BigInt::from(1000); + let big_int_60 = BigInt::from(60); + let big_int_24 = BigInt::from(24); // We only want the biggest subdivision to have the negative sign. let (sign, duration) = if duration.is_zero() || duration.is_positive() { - (1, *duration) + (1, duration.clone()) } else { (-1, -duration) }; - let (micros, nanos): (i64, i64) = duration.div_rem(&1000); - let (millis, micros): (i64, i64) = micros.div_rem(&1000); - let (secs, millis): (i64, i64) = millis.div_rem(&1000); - let (mins, secs): (i64, i64) = secs.div_rem(&60); - let (hours, mins): (i64, i64) = mins.div_rem(&60); - let (days, hours): (i64, i64) = hours.div_rem(&24); + let (micros, nanos): (BigInt, BigInt) = duration.div_rem(&big_int_1000); + let (millis, micros): (BigInt, BigInt) = micros.div_rem(&big_int_1000); + let (secs, millis): (BigInt, BigInt) = millis.div_rem(&big_int_1000); + let (mins, secs): (BigInt, BigInt) = secs.div_rem(&big_int_60); + let (hours, mins): (BigInt, BigInt) = mins.div_rem(&big_int_60); + let (days, hours): (BigInt, BigInt) = hours.div_rem(&big_int_24); let mut output_prep = vec![]; diff --git a/crates/nu_plugin_post/src/post.rs b/crates/nu_plugin_post/src/post.rs index 7e2507879a..c8f06c9a42 100644 --- a/crates/nu_plugin_post/src/post.rs +++ b/crates/nu_plugin_post/src/post.rs @@ -349,9 +349,18 @@ pub fn value_to_json_value(v: &Value) -> Result { UntaggedValue::Primitive(Primitive::Filesize(b)) => serde_json::Value::Number( serde_json::Number::from(b.to_u64().expect("What about really big numbers")), ), - UntaggedValue::Primitive(Primitive::Duration(i)) => { - serde_json::Value::Number(serde_json::Number::from(*i)) - } + UntaggedValue::Primitive(Primitive::Duration(i)) => serde_json::Value::Number( + serde_json::Number::from_f64( + i.to_f64().expect("TODO: What about really big decimals?"), + ) + .ok_or_else(|| { + ShellError::labeled_error( + "Can not convert big decimal to f64", + "cannot convert big decimal to f64", + &v.tag, + ) + })?, + ), UntaggedValue::Primitive(Primitive::Date(d)) => serde_json::Value::String(d.to_string()), UntaggedValue::Primitive(Primitive::EndOfStream) => serde_json::Value::Null, UntaggedValue::Primitive(Primitive::BeginningOfStream) => serde_json::Value::Null,