Spread operator for list literals (#11006)

This commit is contained in:
ysthakur 2023-11-22 16:10:08 -05:00 committed by GitHub
parent 95a745e622
commit 823e578c46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 149 additions and 13 deletions

View File

@ -315,6 +315,7 @@ fn find_matching_block_end_in_expr(
Expr::MatchBlock(_) => None, Expr::MatchBlock(_) => None,
Expr::Nothing => None, Expr::Nothing => None,
Expr::Garbage => None, Expr::Garbage => None,
Expr::Spread(_) => None,
Expr::Table(hdr, rows) => { Expr::Table(hdr, rows) => {
if expr_last == global_cursor_offset { if expr_last == global_cursor_offset {

View File

@ -348,6 +348,12 @@ fn convert_to_value(
"signatures not supported in nuon".into(), "signatures not supported in nuon".into(),
expr.span, 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::String(s) => Ok(Value::string(s, span)),
Expr::StringInterpolation(..) => Err(ShellError::OutsideSpannedLabeledError( Expr::StringInterpolation(..) => Err(ShellError::OutsideSpannedLabeledError(
original_text.to_string(), original_text.to_string(),

View File

@ -538,7 +538,13 @@ pub fn eval_expression(
Expr::List(x) => { Expr::List(x) => {
let mut output = vec![]; let mut output = vec![];
for expr in x { 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)) Ok(Value::list(output, expr.span))
} }
@ -635,6 +641,7 @@ pub fn eval_expression(
Expr::Signature(_) => Ok(Value::nothing(expr.span)), Expr::Signature(_) => Ok(Value::nothing(expr.span)),
Expr::Garbage => Ok(Value::nothing(expr.span)), Expr::Garbage => Ok(Value::nothing(expr.span)),
Expr::Nothing => 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
} }
} }

View File

@ -500,6 +500,15 @@ pub fn flatten_expression(
Expr::VarDecl(var_id) => { Expr::VarDecl(var_id) => {
vec![(expr.span, FlatShape::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
}
} }
} }

View File

@ -3705,7 +3705,7 @@ pub fn parse_list_expression(
working_set.error(err) working_set.error(err)
} }
let (output, err) = lite_parse(&output); let (mut output, err) = lite_parse(&output);
if let Some(err) = err { if let Some(err) = err {
working_set.error(err) working_set.error(err)
} }
@ -3715,24 +3715,55 @@ pub fn parse_list_expression(
let mut contained_type: Option<Type> = None; let mut contained_type: Option<Type> = None;
if !output.block.is_empty() { 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; let mut spans_idx = 0;
if let LiteElement::Command(_, command) = arg { if let LiteElement::Command(_, mut command) = arg {
while spans_idx < command.parts.len() { while spans_idx < command.parts.len() {
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( let arg = parse_multispan_value(
working_set, working_set,
&command.parts, &command.parts,
&mut spans_idx, &mut spans_idx,
element_shape, element_shape,
); );
let ty = arg.ty.clone();
(arg, ty)
};
if let Some(ref ctype) = contained_type { if let Some(ref ctype) = contained_type {
if *ctype != arg.ty { if *ctype != ty {
contained_type = Some(Type::Any); contained_type = Some(Type::Any);
} }
} else { } else {
contained_type = Some(arg.ty.clone()); contained_type = Some(ty);
} }
args.push(arg); args.push(arg);
@ -5870,6 +5901,9 @@ pub fn discover_captures_in_expr(
Expr::VarDecl(var_id) => { Expr::VarDecl(var_id) => {
seen.push(*var_id); seen.push(*var_id);
} }
Expr::Spread(expr) => {
discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
}
} }
Ok(()) Ok(())
} }

View File

@ -45,6 +45,7 @@ pub enum Expr {
Signature(Box<Signature>), Signature(Box<Signature>),
StringInterpolation(Vec<Expression>), StringInterpolation(Vec<Expression>),
MatchPattern(Box<MatchPattern>), MatchPattern(Box<MatchPattern>),
Spread(Box<Expression>),
Nothing, Nothing,
Garbage, Garbage,
} }

View File

@ -289,6 +289,7 @@ impl Expression {
Expr::ValueWithUnit(expr, _) => expr.has_in_variable(working_set), Expr::ValueWithUnit(expr, _) => expr.has_in_variable(working_set),
Expr::Var(var_id) => *var_id == IN_VARIABLE_ID, Expr::Var(var_id) => *var_id == IN_VARIABLE_ID,
Expr::VarDecl(_) => false, Expr::VarDecl(_) => false,
Expr::Spread(expr) => expr.has_in_variable(working_set),
} }
} }
@ -480,6 +481,7 @@ impl Expression {
} }
} }
Expr::VarDecl(_) => {} 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::ValueWithUnit(expr, _) => expr.replace_span(working_set, replaced, new_span),
Expr::Var(_) => {} Expr::Var(_) => {}
Expr::VarDecl(_) => {} Expr::VarDecl(_) => {}
Expr::Spread(expr) => expr.replace_span(working_set, replaced, new_span),
} }
} }
} }

View File

@ -278,7 +278,13 @@ pub fn eval_constant(
Expr::List(x) => { Expr::List(x) => {
let mut output = vec![]; let mut output = vec![];
for expr in x { 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)) Ok(Value::list(output, expr.span))
} }

View File

@ -483,6 +483,10 @@ pub enum ParseError {
#[label("Not allowed here")] Span, #[label("Not allowed here")] Span,
#[label("...and here")] Option<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 { impl ParseError {
@ -569,6 +573,7 @@ impl ParseError {
ParseError::InvalidLiteral(_, _, s) => *s, ParseError::InvalidLiteral(_, _, s) => *s,
ParseError::LabeledErrorWithHelp { span: s, .. } => *s, ParseError::LabeledErrorWithHelp { span: s, .. } => *s,
ParseError::RedirectionInLetMut(s, _) => *s, ParseError::RedirectionInLetMut(s, _) => *s,
ParseError::UnexpectedSpread(s) => *s,
} }
} }
} }

View File

@ -1170,6 +1170,21 @@ This is an internal Nushell error, please file an issue https://github.com/nushe
)] )]
//todo: add error detail //todo: add error detail
ErrorExpandingGlob(String, #[label = "{0}"] Span), 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 // TODO: Implement as From trait

View File

@ -20,6 +20,7 @@ mod test_parser;
mod test_ranges; mod test_ranges;
mod test_regex; mod test_regex;
mod test_signatures; mod test_signatures;
mod test_spread;
mod test_stdlib; mod test_stdlib;
mod test_strings; mod test_strings;
mod test_table_operations; mod test_table_operations;

48
src/tests/test_spread.rs Normal file
View 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>")
}