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::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 {
|
||||||
|
@ -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(),
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 arg = parse_multispan_value(
|
let curr_span = command.parts[spans_idx];
|
||||||
working_set,
|
let curr_tok = working_set.get_span_contents(curr_span);
|
||||||
&command.parts,
|
let (arg, ty) = if curr_tok.starts_with(b"...")
|
||||||
&mut spans_idx,
|
&& curr_tok.len() > 3
|
||||||
element_shape,
|
&& (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 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(())
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
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