allow mut to take pipelines (#9658)

# Description

This extends the syntax fix for `let` (#9589) to `mut` as well.

Example: `mut x = "hello world" | str length; print $x`

closes #9634

# User-Facing Changes

`mut` now joins `let` in being able to be assigned from a pipeline

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A
clippy::needless_collect -A clippy::result_large_err` to check that
you're using the standard code style
- `cargo test --workspace` to check that all tests pass
- `cargo run -- crates/nu-std/tests/run.nu` to run the tests for the
standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# 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.
-->
This commit is contained in:
JT 2023-07-12 06:36:34 +12:00 committed by GitHub
parent 942c66a9f3
commit ad11e25fc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 207 additions and 179 deletions

View File

@ -1,4 +1,4 @@
use nu_engine::eval_expression_with_input; use nu_engine::eval_block;
use nu_protocol::ast::Call; use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type}; use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type};
@ -54,25 +54,25 @@ impl Command for Mut {
.as_var() .as_var()
.expect("internal error: missing variable"); .expect("internal error: missing variable");
let keyword_expr = call let block_id = call
.positional_nth(1) .positional_nth(1)
.expect("checked through parser") .expect("checked through parser")
.as_keyword() .as_block()
.expect("internal error: missing keyword"); .expect("internal error: missing right hand side");
let rhs = eval_expression_with_input( let block = engine_state.get_block(block_id);
let pipeline_data = eval_block(
engine_state, engine_state,
stack, stack,
keyword_expr, block,
input, input,
call.redirect_stdout, call.redirect_stdout,
call.redirect_stderr, call.redirect_stderr,
)? )?;
.0;
//println!("Adding: {:?} to {}", rhs, var_id); //println!("Adding: {:?} to {}", rhs, var_id);
stack.add_var(var_id, rhs.into_value(call.head)); stack.add_var(var_id, pipeline_data.into_value(call.head));
Ok(PipelineData::empty()) Ok(PipelineData::empty())
} }

View File

@ -50,6 +50,30 @@ fn let_pipeline_allows_in() {
assert_eq!(actual.out, "21"); assert_eq!(actual.out, "21");
} }
#[test]
fn mut_takes_pipeline() {
let actual = nu!(
cwd: ".", pipeline(
r#"
mut x = "hello world" | str length; print $x
"#
));
assert_eq!(actual.out, "11");
}
#[test]
fn mut_pipeline_allows_in() {
let actual = nu!(
cwd: ".", pipeline(
r#"
def foo [] { mut x = $in | str length; print ($x + 10) }; "hello world" | foo
"#
));
assert_eq!(actual.out, "21");
}
#[ignore] #[ignore]
#[test] #[test]
fn let_with_external_failed() { fn let_with_external_failed() {

View File

@ -3034,47 +3034,51 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
} }
pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline { pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
let name = working_set.get_span_contents(spans[0]); trace!("parsing: mut");
if name == b"mut" { // JT: Disabling check_name because it doesn't work with optional types in the declaration
// if let Some(span) = check_name(working_set, spans) { // if let Some(span) = check_name(working_set, spans) {
// return Pipeline::from_vec(vec![garbage(*span)]); // return Pipeline::from_vec(vec![garbage(*span)]);
// } // }
if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Nothing) { if let Some(decl_id) = working_set.find_decl(b"mut", &Type::Nothing) {
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
if spans.len() >= 4 { if spans.len() >= 4 {
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order // This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
// so that the var-id created by the variable isn't visible in the expression that init it // so that the var-id created by the variable isn't visible in the expression that init it
for span in spans.iter().enumerate() { for span in spans.iter().enumerate() {
let item = working_set.get_span_contents(*span.1); let item = working_set.get_span_contents(*span.1);
if item == b"=" && spans.len() > (span.0 + 1) { if item == b"=" && spans.len() > (span.0 + 1) {
let mut idx = span.0; let (tokens, parse_error) = lex(
let rvalue = parse_multispan_value( working_set.get_span_contents(nu_protocol::span(&spans[(span.0 + 1)..])),
working_set, spans[span.0 + 1].start,
spans, &[],
&mut idx, &[],
&SyntaxShape::Keyword(
b"=".to_vec(),
Box::new(SyntaxShape::MathExpression),
),
);
if idx < (spans.len() - 1) {
working_set
.error(ParseError::ExtraPositional(call_signature, spans[idx + 1]));
}
let mut idx = 0;
let (lvalue, explicit_type) = parse_var_with_opt_type(
working_set,
&spans[1..(span.0)],
&mut idx,
true, true,
); );
if let Some(parse_error) = parse_error {
working_set.parse_errors.push(parse_error)
}
let rvalue_span = nu_protocol::span(&spans[(span.0 + 1)..]);
let rvalue_block = parse_block(working_set, &tokens, rvalue_span, false, true);
let output_type = rvalue_block.output_type();
let block_id = working_set.add_block(rvalue_block);
let rvalue = Expression {
expr: Expr::Block(block_id),
span: rvalue_span,
ty: output_type,
custom_completion: None,
};
let mut idx = 0;
let (lvalue, explicit_type) =
parse_var_with_opt_type(working_set, &spans[1..(span.0)], &mut idx, true);
let var_name = let var_name =
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span)) String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
.trim_start_matches('$') .trim_start_matches('$')
@ -3088,11 +3092,11 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
let rhs_type = rvalue.ty.clone(); let rhs_type = rvalue.ty.clone();
if let Some(explicit_type) = &explicit_type { if let Some(explicit_type) = &explicit_type {
if &rhs_type != explicit_type && explicit_type != &Type::Any { if !type_compatible(explicit_type, &rhs_type) {
working_set.error(ParseError::TypeMismatch( working_set.error(ParseError::TypeMismatch(
explicit_type.clone(), explicit_type.clone(),
rhs_type.clone(), rhs_type.clone(),
rvalue.span, nu_protocol::span(&spans[(span.0 + 1)..]),
)); ));
} }
} }
@ -3106,10 +3110,7 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
let call = Box::new(Call { let call = Box::new(Call {
decl_id, decl_id,
head: spans[0], head: spans[0],
arguments: vec![ arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)],
Argument::Positional(lvalue),
Argument::Positional(rvalue),
],
redirect_stdout: true, redirect_stdout: true,
redirect_stderr: false, redirect_stderr: false,
parser_info: HashMap::new(), parser_info: HashMap::new(),
@ -3133,10 +3134,15 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
ty: output, ty: output,
custom_completion: None, custom_completion: None,
}]); }]);
} } else {
}
working_set.error(ParseError::UnknownState( working_set.error(ParseError::UnknownState(
"internal error: mut statement unparsable".into(), "internal error: let or const statements not found in core language".into(),
span(spans),
))
}
working_set.error(ParseError::UnknownState(
"internal error: let or const statement unparsable".into(),
span(spans), span(spans),
)); ));

View File

@ -5308,10 +5308,12 @@ pub fn parse_pipeline(
pipeline_index: usize, pipeline_index: usize,
) -> Pipeline { ) -> Pipeline {
if pipeline.commands.len() > 1 { if pipeline.commands.len() > 1 {
// Special case: allow `let` to consume the whole pipeline, eg) `let abc = "foo" | str length` // Special case: allow `let` and `mut` to consume the whole pipeline, eg) `let abc = "foo" | str length`
match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(_, command) if !command.parts.is_empty() => { LiteElement::Command(_, command) if !command.parts.is_empty() => {
if working_set.get_span_contents(command.parts[0]) == b"let" { if working_set.get_span_contents(command.parts[0]) == b"let"
|| working_set.get_span_contents(command.parts[0]) == b"mut"
{
let mut new_command = LiteCommand { let mut new_command = LiteCommand {
comments: vec![], comments: vec![],
parts: command.parts.clone(), parts: command.parts.clone(),
@ -5340,8 +5342,8 @@ pub fn parse_pipeline(
parse_builtin_commands(working_set, &new_command, is_subexpression); parse_builtin_commands(working_set, &new_command, is_subexpression);
if pipeline_index == 0 { if pipeline_index == 0 {
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Nothing) let let_decl_id = working_set.find_decl(b"let", &Type::Nothing);
{ let mut_decl_id = working_set.find_decl(b"mut", &Type::Nothing);
for element in pipeline.elements.iter_mut() { for element in pipeline.elements.iter_mut() {
if let PipelineElement::Expression( if let PipelineElement::Expression(
_, _,
@ -5351,7 +5353,9 @@ pub fn parse_pipeline(
}, },
) = element ) = element
{ {
if call.decl_id == let_decl_id { if Some(call.decl_id) == let_decl_id
|| Some(call.decl_id) == mut_decl_id
{
// Do an expansion // Do an expansion
if let Some(Expression { if let Some(Expression {
expr: Expr::Block(block_id), expr: Expr::Block(block_id),
@ -5360,8 +5364,7 @@ pub fn parse_pipeline(
{ {
let block = working_set.get_block(*block_id); let block = working_set.get_block(*block_id);
let element = let element = block.pipelines[0].elements[0].clone();
block.pipelines[0].elements[0].clone();
if let PipelineElement::Expression(prepend, expr) = if let PipelineElement::Expression(prepend, expr) =
element element
@ -5369,10 +5372,7 @@ pub fn parse_pipeline(
if expr.has_in_variable(working_set) { if expr.has_in_variable(working_set) {
let new_expr = PipelineElement::Expression( let new_expr = PipelineElement::Expression(
prepend, prepend,
wrap_expr_with_collect( wrap_expr_with_collect(working_set, &expr),
working_set,
&expr,
),
); );
let block = let block =
@ -5384,15 +5384,12 @@ pub fn parse_pipeline(
continue; continue;
} else if element.has_in_variable(working_set) } else if element.has_in_variable(working_set)
&& !is_subexpression && !is_subexpression
{
*element =
wrap_element_with_collect(working_set, element);
}
} else if element.has_in_variable(working_set)
&& !is_subexpression
{ {
*element = wrap_element_with_collect(working_set, element); *element = wrap_element_with_collect(working_set, element);
} }
} else if element.has_in_variable(working_set) && !is_subexpression
{
*element = wrap_element_with_collect(working_set, element);
} }
} }
} }
@ -5482,8 +5479,10 @@ pub fn parse_pipeline(
} => { } => {
let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression); let mut pipeline = parse_builtin_commands(working_set, command, is_subexpression);
let let_decl_id = working_set.find_decl(b"let", &Type::Nothing);
let mut_decl_id = working_set.find_decl(b"mut", &Type::Nothing);
if pipeline_index == 0 { if pipeline_index == 0 {
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Nothing) {
for element in pipeline.elements.iter_mut() { for element in pipeline.elements.iter_mut() {
if let PipelineElement::Expression( if let PipelineElement::Expression(
_, _,
@ -5493,7 +5492,9 @@ pub fn parse_pipeline(
}, },
) = element ) = element
{ {
if call.decl_id == let_decl_id { if Some(call.decl_id) == let_decl_id
|| Some(call.decl_id) == mut_decl_id
{
// Do an expansion // Do an expansion
if let Some(Expression { if let Some(Expression {
expr: Expr::Block(block_id), expr: Expr::Block(block_id),
@ -5504,8 +5505,7 @@ pub fn parse_pipeline(
let element = block.pipelines[0].elements[0].clone(); let element = block.pipelines[0].elements[0].clone();
if let PipelineElement::Expression(prepend, expr) = element if let PipelineElement::Expression(prepend, expr) = element {
{
if expr.has_in_variable(working_set) { if expr.has_in_variable(working_set) {
let new_expr = PipelineElement::Expression( let new_expr = PipelineElement::Expression(
prepend, prepend,
@ -5518,8 +5518,7 @@ pub fn parse_pipeline(
} }
} }
continue; continue;
} else if element.has_in_variable(working_set) && !is_subexpression } else if element.has_in_variable(working_set) && !is_subexpression {
{
*element = wrap_element_with_collect(working_set, element); *element = wrap_element_with_collect(working_set, element);
} }
} else if element.has_in_variable(working_set) && !is_subexpression { } else if element.has_in_variable(working_set) && !is_subexpression {
@ -5527,7 +5526,6 @@ pub fn parse_pipeline(
} }
} }
} }
}
pipeline pipeline
} }
LiteElement::SameTargetRedirection { LiteElement::SameTargetRedirection {