nushell/crates/nu-command/tests/commands/math/mod.rs
Ian Manske 4d3283e235
Change append operator to concatenation operator (#14344)
# Description

The "append" operator currently serves as both the append operator and
the concatenation operator. This dual role creates ambiguity when
operating on nested lists.

```nu
[1 2] ++ 3     # appends a value to a list [1 2 3]
[1 2] ++ [3 4] # concatenates two lists    [1 2 3 4]

[[1 2] [3 4]] ++ [5 6]
# does this give [[1 2] [3 4] [5 6]]
# or             [[1 2] [3 4] 5 6]  
```

Another problem is that `++=` can change the type of a variable:
```nu
mut str = 'hello '
$str ++= ['world']
($str | describe) == list<string>
```

Note that appending is only relevant for lists, but concatenation is
relevant for lists, strings, and binary values. Additionally, appending
can be expressed in terms of concatenation (see example below). So, this
PR changes the `++` operator to only perform concatenation.

# User-Facing Changes

Using the `++` operator with a list and a non-list value will now be a
compile time or runtime error.
```nu
mut list = []
$list ++= 1 # error
```
Instead, concatenate a list with one element:
```nu
$list ++= [1]
```
Or use `append`:
```nu
$list = $list | append 1
```

# After Submitting

Update book and docs.

---------

Co-authored-by: Douglas <32344964+NotTheDr01ds@users.noreply.github.com>
2024-11-24 10:59:54 -08:00

524 lines
8.6 KiB
Rust

mod abs;
mod avg;
mod ceil;
mod floor;
mod log;
mod max;
mod median;
mod min;
mod mode;
mod product;
mod round;
mod sqrt;
mod stddev;
mod sum;
mod variance;
use nu_test_support::{nu, pipeline};
#[test]
fn one_arg() {
let actual = nu!(pipeline(
r#"
1
"#
));
assert_eq!(actual.out, "1");
}
#[test]
fn add() {
let actual = nu!(pipeline(
r#"
1 + 1
"#
));
assert_eq!(actual.out, "2");
}
#[test]
fn add_compound() {
let actual = nu!(pipeline(
r#"
1 + 2 + 2
"#
));
assert_eq!(actual.out, "5");
}
#[test]
fn precedence_of_operators() {
let actual = nu!(pipeline(
r#"
1 + 2 * 2
"#
));
assert_eq!(actual.out, "5");
}
#[test]
fn precedence_of_operators2() {
let actual = nu!(pipeline(
r#"
1 + 2 * 2 + 1
"#
));
assert_eq!(actual.out, "6");
}
#[test]
fn precedence_of_operators3() {
let actual = nu!(pipeline(
r#"
5 - 5 * 10 + 5
"#
));
assert_eq!(actual.out, "-40");
}
#[test]
fn precedence_of_operators4() {
let actual = nu!(pipeline(
r#"
5 - (5 * 10) + 5
"#
));
assert_eq!(actual.out, "-40");
}
#[test]
fn division_of_ints() {
let actual = nu!(pipeline(
r#"
4 / 2
"#
));
assert_eq!(actual.out, "2");
}
#[test]
fn division_of_ints2() {
let actual = nu!(pipeline(
r#"
1 / 4
"#
));
assert_eq!(actual.out, "0.25");
}
#[test]
fn error_zero_division_int_int() {
let actual = nu!(pipeline(
r#"
1 / 0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_division_float_int() {
let actual = nu!(pipeline(
r#"
1.0 / 0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_division_int_float() {
let actual = nu!(pipeline(
r#"
1 / 0.0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_division_float_float() {
let actual = nu!(pipeline(
r#"
1.0 / 0.0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn floor_division_of_ints() {
let actual = nu!(pipeline(
r#"
5 // 2
"#
));
assert_eq!(actual.out, "2");
}
#[test]
fn floor_division_of_ints2() {
let actual = nu!(pipeline(
r#"
-3 // 2
"#
));
assert_eq!(actual.out, "-2");
}
#[test]
fn floor_division_of_floats() {
let actual = nu!(pipeline(
r#"
-3.0 // 2.0
"#
));
assert_eq!(actual.out, "-2");
}
#[test]
fn error_zero_floor_division_int_int() {
let actual = nu!(pipeline(
r#"
1 // 0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_floor_division_float_int() {
let actual = nu!(pipeline(
r#"
1.0 // 0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_floor_division_int_float() {
let actual = nu!(pipeline(
r#"
1 // 0.0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn error_zero_floor_division_float_float() {
let actual = nu!(pipeline(
r#"
1.0 // 0.0
"#
));
assert!(actual.err.contains("division by zero"));
}
#[test]
fn proper_precedence_history() {
let actual = nu!(pipeline(
r#"
2 / 2 / 2 + 1
"#
));
assert_eq!(actual.out, "1.5");
}
#[test]
fn parens_precedence() {
let actual = nu!(pipeline(
r#"
4 * (6 - 3)
"#
));
assert_eq!(actual.out, "12");
}
#[test]
fn modulo() {
let actual = nu!(pipeline(
r#"
9 mod 2
"#
));
assert_eq!(actual.out, "1");
}
#[test]
fn floor_div_mod() {
let actual = nu!("let q = 8 // -3; let r = 8 mod -3; 8 == $q * -3 + $r");
assert_eq!(actual.out, "true");
let actual = nu!("let q = -8 // 3; let r = -8 mod 3; -8 == $q * 3 + $r");
assert_eq!(actual.out, "true");
}
#[test]
fn floor_div_mod_overflow() {
let actual = nu!(format!("{} // -1", i64::MIN));
assert!(actual.err.contains("overflow"));
let actual = nu!(format!("{} mod -1", i64::MIN));
assert!(actual.err.contains("overflow"));
}
#[test]
fn floor_div_mod_zero() {
let actual = nu!("1 // 0");
assert!(actual.err.contains("zero"));
let actual = nu!("1 mod 0");
assert!(actual.err.contains("zero"));
}
#[test]
fn floor_div_mod_large_num() {
let actual = nu!(format!("{} // {}", i64::MAX, i64::MAX / 2));
assert_eq!(actual.out, "2");
let actual = nu!(format!("{} mod {}", i64::MAX, i64::MAX / 2));
assert_eq!(actual.out, "1");
}
#[test]
fn unit_multiplication_math() {
let actual = nu!(pipeline(
r#"
1mb * 2
"#
));
assert_eq!(actual.out, "1.9 MiB");
}
#[test]
fn unit_multiplication_float_math() {
let actual = nu!(pipeline(
r#"
1mb * 1.2
"#
));
assert_eq!(actual.out, "1.1 MiB");
}
#[test]
fn unit_float_floor_division_math() {
let actual = nu!(pipeline(
r#"
1mb // 3.0
"#
));
assert_eq!(actual.out, "325.5 KiB");
}
#[test]
fn unit_division_math() {
let actual = nu!(pipeline(
r#"
1mb / 4
"#
));
assert_eq!(actual.out, "244.1 KiB");
}
#[test]
fn unit_float_division_math() {
let actual = nu!(pipeline(
r#"
1mb / 3.1
"#
));
assert_eq!(actual.out, "315.0 KiB");
}
#[test]
fn duration_math() {
let actual = nu!(pipeline(
r#"
1wk + 1day
"#
));
assert_eq!(actual.out, "1wk 1day");
}
#[test]
fn duration_decimal_math() {
let actual = nu!(pipeline(
r#"
5.5day + 0.5day
"#
));
assert_eq!(actual.out, "6day");
}
#[test]
fn duration_math_with_nanoseconds() {
let actual = nu!(pipeline(
r#"
1wk + 10ns
"#
));
assert_eq!(actual.out, "1wk 10ns");
}
#[test]
fn duration_decimal_math_with_nanoseconds() {
let actual = nu!(pipeline(
r#"
1.5wk + 10ns
"#
));
assert_eq!(actual.out, "1wk 3day 10ns");
}
#[test]
fn duration_decimal_math_with_all_units() {
let actual = nu!(pipeline(
r#"
1wk + 3day + 8hr + 10min + 16sec + 121ms + 11us + 12ns
"#
));
assert_eq!(actual.out, "1wk 3day 8hr 10min 16sec 121ms 11µs 12ns");
}
#[test]
fn duration_decimal_dans_test() {
let actual = nu!(pipeline(
r#"
3.14sec
"#
));
assert_eq!(actual.out, "3sec 140ms");
}
#[test]
fn duration_math_with_negative() {
let actual = nu!(pipeline(
r#"
1day - 1wk
"#
));
assert_eq!(actual.out, "-6day");
}
#[test]
fn compound_comparison() {
let actual = nu!(pipeline(
r#"
4 > 3 and 2 > 1
"#
));
assert_eq!(actual.out, "true");
}
#[test]
fn compound_comparison2() {
let actual = nu!(pipeline(
r#"
4 < 3 or 2 > 1
"#
));
assert_eq!(actual.out, "true");
}
#[test]
fn compound_where() {
let actual = nu!(pipeline(
r#"
echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where a == 2 and b == 1 | to json -r
"#
));
assert_eq!(actual.out, r#"[{"a":2,"b":1}]"#);
}
#[test]
fn compound_where_paren() {
let actual = nu!(pipeline(
r#"
echo '[{"a": 1, "b": 1}, {"a": 2, "b": 1}, {"a": 2, "b": 2}]' | from json | where ($it.a == 2 and $it.b == 1) or $it.b == 2 | to json -r
"#
));
assert_eq!(actual.out, r#"[{"a":2,"b":1},{"a":2,"b":2}]"#);
}
// TODO: these ++ tests are not really testing *math* functionality, maybe find another place for them
#[test]
fn concat_lists() {
let actual = nu!(pipeline(
r#"
[1 3] ++ [5 6] | to nuon
"#
));
assert_eq!(actual.out, "[1, 3, 5, 6]");
}
#[test]
fn concat_tables() {
let actual = nu!(pipeline(
r#"
[[a b]; [1 2]] ++ [[c d]; [10 11]] | to nuon
"#
));
assert_eq!(actual.out, "[{a: 1, b: 2}, {c: 10, d: 11}]");
}
#[test]
fn concat_strings() {
let actual = nu!(pipeline(
r#"
"foo" ++ "bar"
"#
));
assert_eq!(actual.out, "foobar");
}
#[test]
fn concat_binary_values() {
let actual = nu!(pipeline(
r#"
0x[01 02] ++ 0x[03 04] | to nuon
"#
));
assert_eq!(actual.out, "0x[01020304]");
}