mirror of
https://github.com/nushell/nushell.git
synced 2025-08-14 23:42:35 +02:00
feat: null
values can be spread as if they are empty lists or records. (#16399)
# Description Spread operator `...` treats `null` values as empty collections, , whichever of list or record is appropriate. # User-Facing Changes `null` values can be used with the spread operator (`...`)
This commit is contained in:
@ -612,12 +612,13 @@ fn eval_instruction<D: DebugContext>(
|
||||
let items = ctx.collect_reg(*items, *span)?;
|
||||
let list_span = list_value.span();
|
||||
let items_span = items.span();
|
||||
let items = match items {
|
||||
Value::List { vals, .. } => vals,
|
||||
Value::Nothing { .. } => Vec::new(),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: items_span }),
|
||||
};
|
||||
let mut list = list_value.into_list()?;
|
||||
list.extend(
|
||||
items
|
||||
.into_list()
|
||||
.map_err(|_| ShellError::CannotSpreadAsList { span: items_span })?,
|
||||
);
|
||||
list.extend(items);
|
||||
ctx.put_reg(*src_dst, Value::list(list, list_span).into_pipeline_data());
|
||||
Ok(Continue)
|
||||
}
|
||||
@ -649,11 +650,13 @@ fn eval_instruction<D: DebugContext>(
|
||||
let record_span = record_value.span();
|
||||
let items_span = items.span();
|
||||
let mut record = record_value.into_record()?;
|
||||
let items = match items {
|
||||
Value::Record { val, .. } => val.into_owned(),
|
||||
Value::Nothing { .. } => Record::new(),
|
||||
_ => return Err(ShellError::CannotSpreadAsRecord { span: items_span }),
|
||||
};
|
||||
// Not using .extend() here because it doesn't handle duplicates
|
||||
for (key, val) in items
|
||||
.into_record()
|
||||
.map_err(|_| ShellError::CannotSpreadAsRecord { span: items_span })?
|
||||
{
|
||||
for (key, val) in items {
|
||||
if let Some(first_value) = record.insert(&key, val) {
|
||||
return Err(ShellError::ColumnDefinedTwice {
|
||||
col_name: key,
|
||||
@ -1191,19 +1194,19 @@ fn gather_arguments(
|
||||
vals,
|
||||
span: spread_span,
|
||||
..
|
||||
} => {
|
||||
if let Value::List { vals, .. } = vals {
|
||||
} => match vals {
|
||||
Value::List { vals, .. } => {
|
||||
rest.extend(vals);
|
||||
// Rest variable should span the spread syntax, not the list values
|
||||
rest_span = Some(rest_span.map_or(spread_span, |s| s.append(spread_span)));
|
||||
// All further positional args should go to spread
|
||||
always_spread = true;
|
||||
} else if let Value::Error { error, .. } = vals {
|
||||
return Err(*error);
|
||||
} else {
|
||||
return Err(ShellError::CannotSpreadAsList { span: vals.span() });
|
||||
}
|
||||
}
|
||||
Value::Nothing { .. } => {
|
||||
rest_span = Some(rest_span.map_or(spread_span, |s| s.append(spread_span)));
|
||||
always_spread = true;
|
||||
}
|
||||
Value::Error { error, .. } => return Err(*error),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: vals.span() }),
|
||||
},
|
||||
Argument::Flag {
|
||||
data,
|
||||
name,
|
||||
|
@ -316,6 +316,7 @@ impl Call {
|
||||
if spread {
|
||||
match result {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
Value::Nothing { .. } => (),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
}
|
||||
} else {
|
||||
|
@ -78,6 +78,7 @@ pub trait Eval {
|
||||
ListItem::Item(expr) => output.push(Self::eval::<D>(state, mut_state, expr)?),
|
||||
ListItem::Spread(_, expr) => match Self::eval::<D>(state, mut_state, expr)? {
|
||||
Value::List { vals, .. } => output.extend(vals),
|
||||
Value::Nothing { .. } => (),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr_span }),
|
||||
},
|
||||
}
|
||||
|
@ -187,6 +187,7 @@ impl Call {
|
||||
if spread {
|
||||
match rest_val {
|
||||
Value::List { vals, .. } => acc.extend(vals.iter().cloned()),
|
||||
Value::Nothing { .. } => (),
|
||||
Value::Error { error, .. } => return Err(ShellError::clone(error)),
|
||||
_ => {
|
||||
return Err(ShellError::CannotSpreadAsList {
|
||||
|
@ -200,3 +200,29 @@ fn respect_shape() -> TestResult {
|
||||
run_test(r#"def "...$foo" [] {2}; do { ...$foo }"#, "2").unwrap();
|
||||
run_test(r#"match "...$foo" { ...$foo => 5 }"#, "5")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_null() -> TestResult {
|
||||
// Spread in list
|
||||
run_test(r#"[1, 2, ...(null)] | to nuon --raw"#, r#"[1,2]"#)?;
|
||||
|
||||
// Spread in record
|
||||
run_test(r#"{a: 1, b: 2, ...(null)} | to nuon --raw"#, r#"{a:1,b:2}"#)?;
|
||||
|
||||
// Spread to built-in command's ...rest
|
||||
run_test(r#"echo 1 2 ...(null) | to nuon --raw"#, r#"[1,2]"#)?;
|
||||
|
||||
// Spread to custom command's ...rest
|
||||
run_test(
|
||||
r#"
|
||||
def foo [...rest] { $rest }
|
||||
foo ...(null) 1 2 ...(null) 3 | to nuon --raw
|
||||
"#,
|
||||
r#"[1,2,3]"#,
|
||||
)?;
|
||||
|
||||
// Spread to external command's arguments
|
||||
assert_eq!(nu!(r#"nu --testbin cococo 1 ...(null) 2"#).out, "1 2");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
Reference in New Issue
Block a user