mirror of
https://github.com/nushell/nushell.git
synced 2025-08-19 07:41:30 +02:00
Box ShellError
in Value::Error
(#8375)
# Description Our `ShellError` at the moment has a `std::mem::size_of<ShellError>` of 136 bytes (on AMD64). As a result `Value` directly storing the struct also required 136 bytes (thanks to alignment requirements). This change stores the `Value::Error` `ShellError` on the heap. Pro: - Value now needs just 80 bytes - Should be 1 cacheline less (still at least 2 cachelines) Con: - More small heap allocations when dealing with `Value::Error` - More heap fragmentation - Potential for additional required memcopies # Further code changes Includes a small refactor of `try` due to a type mismatch in its large match. # User-Facing Changes None for regular users. Plugin authors may have to update their matches on `Value` if they use `nu-protocol` Needs benchmarking to see if there is a benefit in real world workloads. **Update** small improvements in runtime for workloads with high volume of values. Significant reduction in maximum resident set size, when many values are held in memory. # Tests + Formatting
This commit is contained in:
committed by
GitHub
parent
c26d91fb61
commit
a52386e837
@@ -193,12 +193,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "int, filesize, float, string".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -87,12 +87,12 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "integer or filesize".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -188,13 +188,13 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "integer, float, filesize, string, date, duration, binary or bool"
|
||||
.into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -161,17 +161,19 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
},
|
||||
Value::String { val, .. } => match string_to_boolean(val, span) {
|
||||
Ok(val) => Value::Bool { val, span },
|
||||
Err(error) => Value::Error { error },
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
},
|
||||
},
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "bool, integer, float or string".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -209,12 +209,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
Value::Error { .. } => return input.clone(),
|
||||
other => {
|
||||
return Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string and integer".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -245,21 +245,21 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
|
||||
Some(eastoffset) => match_datetime!(eastoffset.timestamp_nanos(ts)),
|
||||
None => Value::Error {
|
||||
error: ShellError::DatetimeParseError(input.debug_value(), *span),
|
||||
error: Box::new(ShellError::DatetimeParseError(input.debug_value(), *span)),
|
||||
},
|
||||
},
|
||||
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
|
||||
Some(westoffset) => match_datetime!(westoffset.timestamp_nanos(ts)),
|
||||
None => Value::Error {
|
||||
error: ShellError::DatetimeParseError(input.debug_value(), *span),
|
||||
error: Box::new(ShellError::DatetimeParseError(input.debug_value(), *span)),
|
||||
},
|
||||
},
|
||||
Zone::Error => Value::Error {
|
||||
// This is an argument error, not an input error
|
||||
error: ShellError::TypeMismatch {
|
||||
error: Box::new(ShellError::TypeMismatch {
|
||||
err_message: "Invalid timezone or offset".to_string(),
|
||||
span: *span,
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -273,7 +273,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
Ok(d) => Value::Date { val: d, span: head },
|
||||
Err(reason) => {
|
||||
Value::Error {
|
||||
error: ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
|
||||
error: Box::new(ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }),
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -292,12 +292,12 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -374,7 +374,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn takes_timestamp_offset_as_int() {
|
||||
let date_int = Value::test_int(1614434140_000000000);
|
||||
let date_int = Value::test_int(1_614_434_140_000_000_000);
|
||||
let timezone_option = Some(Spanned {
|
||||
item: Zone::East(8),
|
||||
span: Span::test_data(),
|
||||
|
@@ -88,12 +88,12 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||
match other.parse::<f64>() {
|
||||
Ok(x) => Value::float(x, head),
|
||||
Err(reason) => Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "float".to_string(),
|
||||
from_type: reason.to_string(),
|
||||
span: *span,
|
||||
help: None,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -108,12 +108,12 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string, integer or bool".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -174,7 +174,9 @@ fn into_duration(
|
||||
Box::new(move |old| action(old, &d, float_precision, head)),
|
||||
);
|
||||
if let Err(error) = r {
|
||||
return Value::Error { error };
|
||||
return Value::Error {
|
||||
error: Box::new(error),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,7 +432,7 @@ fn action(
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
Err(e) => Value::Error { error: Box::new(e) },
|
||||
}
|
||||
} else {
|
||||
input.clone()
|
||||
@@ -464,34 +466,36 @@ fn action(
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => Value::Error { error: e },
|
||||
Err(e) => Value::Error { error: Box::new(e) },
|
||||
}
|
||||
} else {
|
||||
Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
from_type: "duration".into(),
|
||||
span,
|
||||
help: None,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match string_to_duration(val, span, *value_span) {
|
||||
Ok(val) => Value::Duration { val, span },
|
||||
Err(error) => Value::Error { error },
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string or duration".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@@ -110,19 +110,21 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
|
||||
val,
|
||||
span: value_span,
|
||||
},
|
||||
Err(error) => Value::Error { error },
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
},
|
||||
},
|
||||
Value::Nothing { .. } => Value::Filesize {
|
||||
val: 0,
|
||||
span: value_span,
|
||||
},
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string and integer".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: value_span,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
@@ -189,12 +189,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
Ok(v) => v,
|
||||
_ => {
|
||||
return Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "float".to_string(),
|
||||
from_type: "integer".to_string(),
|
||||
span,
|
||||
help: None,
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +206,9 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
if radix == 10 {
|
||||
match int_from_string(val, span) {
|
||||
Ok(val) => Value::Int { val, span },
|
||||
Err(error) => Value::Error { error },
|
||||
Err(error) => Value::Error {
|
||||
error: Box::new(error),
|
||||
},
|
||||
}
|
||||
} else {
|
||||
convert_int(input, span, radix)
|
||||
@@ -232,10 +234,10 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
.unwrap()
|
||||
{
|
||||
Value::Error {
|
||||
error: ShellError::IncorrectValue {
|
||||
error: Box::new(ShellError::IncorrectValue {
|
||||
msg: "DateTime out of range for timestamp: 1677-09-21T00:12:43Z to 2262-04-11T23:47:16".to_string(),
|
||||
span
|
||||
},
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
@@ -269,13 +271,13 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
// Propagate errors by explicitly matching them before the final case.
|
||||
Value::Error { .. } => input.clone(),
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "integer, float, filesize, date, string, binary, duration or bool"
|
||||
.into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: span,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -292,7 +294,7 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
||||
{
|
||||
match int_from_string(val, head) {
|
||||
Ok(x) => return Value::int(x, head),
|
||||
Err(e) => return Value::Error { error: e },
|
||||
Err(e) => return Value::Error { error: Box::new(e) },
|
||||
}
|
||||
} else if val.starts_with("00") {
|
||||
// It's a padded string
|
||||
@@ -300,12 +302,12 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
||||
Ok(n) => return Value::int(n, head),
|
||||
Err(e) => {
|
||||
return Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "string".to_string(),
|
||||
from_type: "int".to_string(),
|
||||
span: head,
|
||||
help: Some(e.to_string()),
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -316,24 +318,24 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
|
||||
Value::Error { .. } => return input.clone(),
|
||||
other => {
|
||||
return Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string and integer".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: head,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
};
|
||||
match i64::from_str_radix(i.trim(), radix) {
|
||||
Ok(n) => Value::int(n, head),
|
||||
Err(_reason) => Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "string".to_string(),
|
||||
from_type: "int".to_string(),
|
||||
span: head,
|
||||
help: None,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -483,9 +485,9 @@ mod test {
|
||||
}
|
||||
|
||||
#[rstest]
|
||||
#[case("2262-04-11T23:47:16+00:00", 0x7fffffff_ffffffff)]
|
||||
#[case("2262-04-11T23:47:16+00:00", 0x7fff_ffff_ffff_ffff)]
|
||||
#[case("1970-01-01T00:00:00+00:00", 0)]
|
||||
#[case("1677-09-21T00:12:44+00:00", -0x7fffffff_ffffffff)]
|
||||
#[case("1677-09-21T00:12:44+00:00", -0x7fff_ffff_ffff_ffff)]
|
||||
fn datetime_to_int_values_that_work(
|
||||
#[case] dt_in: DateTime<FixedOffset>,
|
||||
#[case] int_expected: i64,
|
||||
@@ -522,14 +524,15 @@ mod test {
|
||||
},
|
||||
Span::test_data(),
|
||||
);
|
||||
if let Value::Error {
|
||||
error: ShellError::IncorrectValue { msg: e, .. },
|
||||
} = actual
|
||||
{
|
||||
assert!(
|
||||
e.contains(err_expected),
|
||||
"{e:?} doesn't contain {err_expected}"
|
||||
);
|
||||
if let Value::Error { error } = actual {
|
||||
if let ShellError::IncorrectValue { msg: e, .. } = *error {
|
||||
assert!(
|
||||
e.contains(err_expected),
|
||||
"{e:?} doesn't contain {err_expected}"
|
||||
);
|
||||
} else {
|
||||
panic!("Unexpected error variant {error:?}")
|
||||
}
|
||||
} else {
|
||||
panic!("Unexpected actual value {actual:?}")
|
||||
}
|
||||
|
@@ -185,12 +185,12 @@ fn into_record(
|
||||
Value::Record { cols, vals, span } => Value::Record { cols, vals, span },
|
||||
Value::Error { .. } => input,
|
||||
other => Value::Error {
|
||||
error: ShellError::OnlySupportsThisInputType {
|
||||
error: Box::new(ShellError::OnlySupportsThisInputType {
|
||||
exp_input_type: "string".into(),
|
||||
wrong_type: other.get_type().to_string(),
|
||||
dst_span: call.head,
|
||||
src_span: other.expect_span(),
|
||||
},
|
||||
}),
|
||||
},
|
||||
};
|
||||
Ok(res.into_pipeline_data())
|
||||
|
@@ -247,28 +247,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
|
||||
span: _,
|
||||
} => Value::Error {
|
||||
// Watch out for CantConvert's argument order
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
from_type: "record".into(),
|
||||
span,
|
||||
help: Some("try using the `to nuon` command".into()),
|
||||
},
|
||||
}),
|
||||
},
|
||||
Value::Binary { .. } => Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: "string".into(),
|
||||
from_type: "binary".into(),
|
||||
span,
|
||||
help: Some("try using the `decode` command".into()),
|
||||
},
|
||||
}),
|
||||
},
|
||||
x => Value::Error {
|
||||
error: ShellError::CantConvert {
|
||||
error: Box::new(ShellError::CantConvert {
|
||||
to_type: String::from("string"),
|
||||
from_type: x.get_type().to_string(),
|
||||
span,
|
||||
help: None,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user