forked from extern/nushell
Spread operator for list literals (#11006)
This commit is contained in:
parent
95a745e622
commit
823e578c46
@ -315,6 +315,7 @@ fn find_matching_block_end_in_expr(
|
||||
Expr::MatchBlock(_) => None,
|
||||
Expr::Nothing => None,
|
||||
Expr::Garbage => None,
|
||||
Expr::Spread(_) => None,
|
||||
|
||||
Expr::Table(hdr, rows) => {
|
||||
if expr_last == global_cursor_offset {
|
||||
|
@ -348,6 +348,12 @@ fn convert_to_value(
|
||||
"signatures not supported in nuon".into(),
|
||||
expr.span,
|
||||
)),
|
||||
Expr::Spread(..) => Err(ShellError::OutsideSpannedLabeledError(
|
||||
original_text.to_string(),
|
||||
"Error when loading".into(),
|
||||
"spread operator not supported in nuon".into(),
|
||||
expr.span,
|
||||
)),
|
||||
Expr::String(s) => Ok(Value::string(s, span)),
|
||||
Expr::StringInterpolation(..) => Err(ShellError::OutsideSpannedLabeledError(
|
||||
original_text.to_string(),
|
||||
|
@ -538,7 +538,13 @@ pub fn eval_expression(
|
||||
Expr::List(x) => {
|
||||
let mut output = vec![];
|
||||
for expr in x {
|
||||
output.push(eval_expression(engine_state, stack, expr)?);
|
||||
match &expr.expr {
|
||||
Expr::Spread(expr) => match eval_expression(engine_state, stack, expr)? {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
},
|
||||
_ => output.push(eval_expression(engine_state, stack, expr)?),
|
||||
}
|
||||
}
|
||||
Ok(Value::list(output, expr.span))
|
||||
}
|
||||
@ -635,6 +641,7 @@ pub fn eval_expression(
|
||||
Expr::Signature(_) => Ok(Value::nothing(expr.span)),
|
||||
Expr::Garbage => Ok(Value::nothing(expr.span)),
|
||||
Expr::Nothing => Ok(Value::nothing(expr.span)),
|
||||
Expr::Spread(_) => Ok(Value::nothing(expr.span)), // Spread operator only occurs in lists
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -500,6 +500,15 @@ pub fn flatten_expression(
|
||||
Expr::VarDecl(var_id) => {
|
||||
vec![(expr.span, FlatShape::VarDecl(*var_id))]
|
||||
}
|
||||
|
||||
Expr::Spread(inner_expr) => {
|
||||
let mut output = vec![(
|
||||
Span::new(expr.span.start, expr.span.start + 3),
|
||||
FlatShape::Operator,
|
||||
)];
|
||||
output.extend(flatten_expression(working_set, inner_expr));
|
||||
output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3705,7 +3705,7 @@ pub fn parse_list_expression(
|
||||
working_set.error(err)
|
||||
}
|
||||
|
||||
let (output, err) = lite_parse(&output);
|
||||
let (mut output, err) = lite_parse(&output);
|
||||
if let Some(err) = err {
|
||||
working_set.error(err)
|
||||
}
|
||||
@ -3715,24 +3715,55 @@ pub fn parse_list_expression(
|
||||
let mut contained_type: Option<Type> = None;
|
||||
|
||||
if !output.block.is_empty() {
|
||||
for arg in &output.block[0].commands {
|
||||
for arg in output.block.remove(0).commands {
|
||||
let mut spans_idx = 0;
|
||||
|
||||
if let LiteElement::Command(_, command) = arg {
|
||||
if let LiteElement::Command(_, mut command) = arg {
|
||||
while spans_idx < command.parts.len() {
|
||||
let arg = parse_multispan_value(
|
||||
working_set,
|
||||
&command.parts,
|
||||
&mut spans_idx,
|
||||
element_shape,
|
||||
);
|
||||
let curr_span = command.parts[spans_idx];
|
||||
let curr_tok = working_set.get_span_contents(curr_span);
|
||||
let (arg, ty) = if curr_tok.starts_with(b"...")
|
||||
&& curr_tok.len() > 3
|
||||
&& (curr_tok[3] == b'$' || curr_tok[3] == b'[' || curr_tok[3] == b'(')
|
||||
{
|
||||
// Parse the spread operator
|
||||
// Remove "..." before parsing argument to spread operator
|
||||
command.parts[spans_idx] = Span::new(curr_span.start + 3, curr_span.end);
|
||||
let spread_arg = parse_multispan_value(
|
||||
working_set,
|
||||
&command.parts,
|
||||
&mut spans_idx,
|
||||
&SyntaxShape::List(Box::new(element_shape.clone())),
|
||||
);
|
||||
let elem_ty = match &spread_arg.ty {
|
||||
Type::List(elem_ty) => *elem_ty.clone(),
|
||||
_ => Type::Any,
|
||||
};
|
||||
let span = Span::new(curr_span.start, spread_arg.span.end);
|
||||
let spread_expr = Expression {
|
||||
expr: Expr::Spread(Box::new(spread_arg)),
|
||||
span,
|
||||
ty: elem_ty.clone(),
|
||||
custom_completion: None,
|
||||
};
|
||||
(spread_expr, elem_ty)
|
||||
} else {
|
||||
let arg = parse_multispan_value(
|
||||
working_set,
|
||||
&command.parts,
|
||||
&mut spans_idx,
|
||||
element_shape,
|
||||
);
|
||||
let ty = arg.ty.clone();
|
||||
(arg, ty)
|
||||
};
|
||||
|
||||
if let Some(ref ctype) = contained_type {
|
||||
if *ctype != arg.ty {
|
||||
if *ctype != ty {
|
||||
contained_type = Some(Type::Any);
|
||||
}
|
||||
} else {
|
||||
contained_type = Some(arg.ty.clone());
|
||||
contained_type = Some(ty);
|
||||
}
|
||||
|
||||
args.push(arg);
|
||||
@ -5870,6 +5901,9 @@ pub fn discover_captures_in_expr(
|
||||
Expr::VarDecl(var_id) => {
|
||||
seen.push(*var_id);
|
||||
}
|
||||
Expr::Spread(expr) => {
|
||||
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ pub enum Expr {
|
||||
Signature(Box<Signature>),
|
||||
StringInterpolation(Vec<Expression>),
|
||||
MatchPattern(Box<MatchPattern>),
|
||||
Spread(Box<Expression>),
|
||||
Nothing,
|
||||
Garbage,
|
||||
}
|
||||
|
@ -289,6 +289,7 @@ impl Expression {
|
||||
Expr::ValueWithUnit(expr, _) => expr.has_in_variable(working_set),
|
||||
Expr::Var(var_id) => *var_id == IN_VARIABLE_ID,
|
||||
Expr::VarDecl(_) => false,
|
||||
Expr::Spread(expr) => expr.has_in_variable(working_set),
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,6 +481,7 @@ impl Expression {
|
||||
}
|
||||
}
|
||||
Expr::VarDecl(_) => {}
|
||||
Expr::Spread(expr) => expr.replace_in_variable(working_set, new_var_id),
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,6 +620,7 @@ impl Expression {
|
||||
Expr::ValueWithUnit(expr, _) => expr.replace_span(working_set, replaced, new_span),
|
||||
Expr::Var(_) => {}
|
||||
Expr::VarDecl(_) => {}
|
||||
Expr::Spread(expr) => expr.replace_span(working_set, replaced, new_span),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -278,7 +278,13 @@ pub fn eval_constant(
|
||||
Expr::List(x) => {
|
||||
let mut output = vec![];
|
||||
for expr in x {
|
||||
output.push(eval_constant(working_set, expr)?);
|
||||
match &expr.expr {
|
||||
Expr::Spread(expr) => match eval_constant(working_set, expr)? {
|
||||
Value::List { mut vals, .. } => output.append(&mut vals),
|
||||
_ => return Err(ShellError::CannotSpreadAsList { span: expr.span }),
|
||||
},
|
||||
_ => output.push(eval_constant(working_set, expr)?),
|
||||
}
|
||||
}
|
||||
Ok(Value::list(output, expr.span))
|
||||
}
|
||||
|
@ -483,6 +483,10 @@ pub enum ParseError {
|
||||
#[label("Not allowed here")] Span,
|
||||
#[label("...and here")] Option<Span>,
|
||||
),
|
||||
|
||||
#[error("Unexpected spread operator outside list")]
|
||||
#[diagnostic(code(nu::parser::unexpected_spread_operator))]
|
||||
UnexpectedSpread(#[label("Spread operator not allowed here")] Span),
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
@ -569,6 +573,7 @@ impl ParseError {
|
||||
ParseError::InvalidLiteral(_, _, s) => *s,
|
||||
ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
|
||||
ParseError::RedirectionInLetMut(s, _) => *s,
|
||||
ParseError::UnexpectedSpread(s) => *s,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1170,6 +1170,21 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
|
||||
)]
|
||||
//todo: add error detail
|
||||
ErrorExpandingGlob(String, #[label = "{0}"] Span),
|
||||
|
||||
/// Tried spreading a non-list inside a list.
|
||||
///
|
||||
/// ## Resolution
|
||||
///
|
||||
/// Only lists can be spread inside lists. Try converting the value to a list before spreading.
|
||||
#[error("Not a list")]
|
||||
#[diagnostic(
|
||||
code(nu::shell::cannot_spread),
|
||||
help("Only lists can be spread inside lists. Try converting the value to a list before spreading")
|
||||
)]
|
||||
CannotSpreadAsList {
|
||||
#[label = "cannot spread value"]
|
||||
span: Span,
|
||||
},
|
||||
}
|
||||
|
||||
// TODO: Implement as From trait
|
||||
|
@ -20,6 +20,7 @@ mod test_parser;
|
||||
mod test_ranges;
|
||||
mod test_regex;
|
||||
mod test_signatures;
|
||||
mod test_spread;
|
||||
mod test_stdlib;
|
||||
mod test_strings;
|
||||
mod test_table_operations;
|
||||
|
48
src/tests/test_spread.rs
Normal file
48
src/tests/test_spread.rs
Normal file
@ -0,0 +1,48 @@
|
||||
use crate::tests::{fail_test, run_test, TestResult};
|
||||
|
||||
#[test]
|
||||
fn spread_in_list() -> TestResult {
|
||||
run_test(r#"[...[]] | to nuon"#, "[]").unwrap();
|
||||
run_test(
|
||||
r#"[1 2 ...[[3] {x: 1}] 5] | to nuon"#,
|
||||
"[1, 2, [3], {x: 1}, 5]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"[...("foo" | split chars) 10] | to nuon"#,
|
||||
"[f, o, o, 10]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"let l = [1, 2, [3]]; [...$l $l] | to nuon"#,
|
||||
"[1, 2, [3], [1, 2, [3]]]",
|
||||
)
|
||||
.unwrap();
|
||||
run_test(
|
||||
r#"[ ...[ ...[ ...[ a ] b ] c ] d ] | to nuon"#,
|
||||
"[a, b, c, d]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_spread() -> TestResult {
|
||||
run_test(r#"def ... [x] { $x }; ... ..."#, "...").unwrap();
|
||||
run_test(
|
||||
r#"let a = 4; [... $a ... [1] ... (5) ...bare ...] | to nuon"#,
|
||||
"[..., 4, ..., [1], ..., 5, ...bare, ...]",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_spread_on_non_list() -> TestResult {
|
||||
fail_test(r#"let x = 5; [...$x]"#, "cannot spread").unwrap();
|
||||
fail_test(r#"[...({ x: 1 })]"#, "cannot spread")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spread_type() -> TestResult {
|
||||
run_test(r#"[1 ...[]] | describe"#, "list<int>").unwrap();
|
||||
run_test(r#"[1 ...[2]] | describe"#, "list<int>").unwrap();
|
||||
run_test(r#"["foo" ...[4 5 6]] | describe"#, "list<any>").unwrap();
|
||||
run_test(r#"[1 2 ...["misfit"] 4] | describe"#, "list<any>")
|
||||
}
|
Loading…
Reference in New Issue
Block a user