feat(each): noop on single null input, map-null equivalent (#16396)

# Description
Basically, now `null | each { "something" }` will be `null` instead of
`"something"`. Thanks to this, `each` can be used to map values similar
to `map-null` custom commands, for example:
- Before
```nu
let args = if $delay != null {
    ["--delay" ($delay | format duration sec | parse '{secs} {_}' | get 0.secs)]
} else {
    []
}
```
- After
```nu
let args = (
    $delay
    | each { ["--delay" ($in | format duration sec | parse '{secs} {_}' | get 0.secs)] }
    | default []
)
```

Please let me know if this change messes something up I'm not seeing.
# User-Facing Changes
- Before
```nu
→ null | each { "something" }
something
```
- After
```nu
→ null | each { "something" }
null
```
# Tests + Formatting
Added a test to check for this.

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Bahex <17417311+Bahex@users.noreply.github.com>
This commit is contained in:
Tyarel8
2025-08-13 22:22:24 +02:00
committed by GitHub
parent 43992f5b6f
commit 3fe9c7c00c
3 changed files with 16 additions and 2 deletions

View File

@@ -90,6 +90,11 @@ with 'transpose' first."#
Value::nothing(Span::test_data()), Value::nothing(Span::test_data()),
])), ])),
}, },
Example {
example: r#"$env.name? | each { $"hello ($in)" } | default "bye""#,
description: "Update value if not null, otherwise do nothing",
result: None,
},
] ]
} }
@@ -107,6 +112,7 @@ with 'transpose' first."#
let metadata = input.metadata(); let metadata = input.metadata();
match input { match input {
PipelineData::Empty => Ok(PipelineData::empty()), PipelineData::Empty => Ok(PipelineData::empty()),
PipelineData::Value(Value::Nothing { .. }, ..) => Ok(input),
PipelineData::Value(Value::Range { .. }, ..) PipelineData::Value(Value::Range { .. }, ..)
| PipelineData::Value(Value::List { .. }, ..) | PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream(..) => { | PipelineData::ListStream(..) => {

View File

@@ -63,3 +63,10 @@ fn errors_in_nested_each_show() {
let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }"); let actual = nu!("[[1,2]] | each {|x| $x | each {|y| error make {msg: \"oh noes\"} } }");
assert!(actual.err.contains("oh noes")) assert!(actual.err.contains("oh noes"))
} }
#[test]
fn each_noop_on_single_null() {
let actual = nu!("null | each { \"test\" } | describe");
assert_eq!(actual.out, "nothing");
}

View File

@@ -1,4 +1,4 @@
use crate::repl::tests::{TestResult, fail_test, run_test}; use crate::repl::tests::{TestResult, fail_test, run_test, run_test_contains};
use rstest::rstest; use rstest::rstest;
#[test] #[test]
@@ -87,7 +87,8 @@ fn in_used_in_range_to() -> TestResult {
#[test] #[test]
fn help_works_with_missing_requirements() -> TestResult { fn help_works_with_missing_requirements() -> TestResult {
run_test(r#"each --help | lines | length"#, "72") fail_test(r#"each"#, "missing_positional")?;
run_test_contains(r#"each --help"#, "Usage")
} }
#[rstest] #[rstest]