mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
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:
parent
942c66a9f3
commit
ad11e25fc5
4 changed files with 207 additions and 179 deletions
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -3034,109 +3034,115 @@ 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);
|
if spans.len() >= 4 {
|
||||||
let call_signature = cmd.signature().call_signature();
|
// 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
|
||||||
|
for span in spans.iter().enumerate() {
|
||||||
|
let item = working_set.get_span_contents(*span.1);
|
||||||
|
if item == b"=" && spans.len() > (span.0 + 1) {
|
||||||
|
let (tokens, parse_error) = lex(
|
||||||
|
working_set.get_span_contents(nu_protocol::span(&spans[(span.0 + 1)..])),
|
||||||
|
spans[span.0 + 1].start,
|
||||||
|
&[],
|
||||||
|
&[],
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
if spans.len() >= 4 {
|
if let Some(parse_error) = parse_error {
|
||||||
// This is a bit of by-hand parsing to get around the issue where we want to parse in the reverse order
|
working_set.parse_errors.push(parse_error)
|
||||||
// so that the var-id created by the variable isn't visible in the expression that init it
|
|
||||||
for span in spans.iter().enumerate() {
|
|
||||||
let item = working_set.get_span_contents(*span.1);
|
|
||||||
if item == b"=" && spans.len() > (span.0 + 1) {
|
|
||||||
let mut idx = span.0;
|
|
||||||
let rvalue = parse_multispan_value(
|
|
||||||
working_set,
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
let var_name =
|
|
||||||
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
|
||||||
.trim_start_matches('$')
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
|
|
||||||
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
|
|
||||||
}
|
|
||||||
|
|
||||||
let var_id = lvalue.as_var();
|
|
||||||
let rhs_type = rvalue.ty.clone();
|
|
||||||
|
|
||||||
if let Some(explicit_type) = &explicit_type {
|
|
||||||
if &rhs_type != explicit_type && explicit_type != &Type::Any {
|
|
||||||
working_set.error(ParseError::TypeMismatch(
|
|
||||||
explicit_type.clone(),
|
|
||||||
rhs_type.clone(),
|
|
||||||
rvalue.span,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(var_id) = var_id {
|
|
||||||
if explicit_type.is_none() {
|
|
||||||
working_set.set_variable_type(var_id, rhs_type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let call = Box::new(Call {
|
|
||||||
decl_id,
|
|
||||||
head: spans[0],
|
|
||||||
arguments: vec![
|
|
||||||
Argument::Positional(lvalue),
|
|
||||||
Argument::Positional(rvalue),
|
|
||||||
],
|
|
||||||
redirect_stdout: true,
|
|
||||||
redirect_stderr: false,
|
|
||||||
parser_info: HashMap::new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return Pipeline::from_vec(vec![Expression {
|
|
||||||
expr: Expr::Call(call),
|
|
||||||
span: nu_protocol::span(spans),
|
|
||||||
ty: Type::Any,
|
|
||||||
custom_completion: None,
|
|
||||||
}]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 =
|
||||||
|
String::from_utf8_lossy(working_set.get_span_contents(lvalue.span))
|
||||||
|
.trim_start_matches('$')
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if ["in", "nu", "env", "nothing"].contains(&var_name.as_str()) {
|
||||||
|
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
|
||||||
|
}
|
||||||
|
|
||||||
|
let var_id = lvalue.as_var();
|
||||||
|
let rhs_type = rvalue.ty.clone();
|
||||||
|
|
||||||
|
if let Some(explicit_type) = &explicit_type {
|
||||||
|
if !type_compatible(explicit_type, &rhs_type) {
|
||||||
|
working_set.error(ParseError::TypeMismatch(
|
||||||
|
explicit_type.clone(),
|
||||||
|
rhs_type.clone(),
|
||||||
|
nu_protocol::span(&spans[(span.0 + 1)..]),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(var_id) = var_id {
|
||||||
|
if explicit_type.is_none() {
|
||||||
|
working_set.set_variable_type(var_id, rhs_type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let call = Box::new(Call {
|
||||||
|
decl_id,
|
||||||
|
head: spans[0],
|
||||||
|
arguments: vec![Argument::Positional(lvalue), Argument::Positional(rvalue)],
|
||||||
|
redirect_stdout: true,
|
||||||
|
redirect_stderr: false,
|
||||||
|
parser_info: HashMap::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: nu_protocol::span(spans),
|
||||||
|
ty: Type::Any,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let ParsedInternalCall { call, output } =
|
|
||||||
parse_internal_call(working_set, spans[0], &spans[1..], decl_id);
|
|
||||||
|
|
||||||
return Pipeline::from_vec(vec![Expression {
|
|
||||||
expr: Expr::Call(call),
|
|
||||||
span: nu_protocol::span(spans),
|
|
||||||
ty: output,
|
|
||||||
custom_completion: None,
|
|
||||||
}]);
|
|
||||||
}
|
}
|
||||||
|
let ParsedInternalCall { call, output } =
|
||||||
|
parse_internal_call(working_set, spans[0], &spans[1..], decl_id);
|
||||||
|
|
||||||
|
return Pipeline::from_vec(vec![Expression {
|
||||||
|
expr: Expr::Call(call),
|
||||||
|
span: nu_protocol::span(spans),
|
||||||
|
ty: output,
|
||||||
|
custom_completion: None,
|
||||||
|
}]);
|
||||||
|
} else {
|
||||||
|
working_set.error(ParseError::UnknownState(
|
||||||
|
"internal error: let or const statements not found in core language".into(),
|
||||||
|
span(spans),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
working_set.error(ParseError::UnknownState(
|
working_set.error(ParseError::UnknownState(
|
||||||
"internal error: mut statement unparsable".into(),
|
"internal error: let or const statement unparsable".into(),
|
||||||
span(spans),
|
span(spans),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
|
|
@ -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,59 +5342,54 @@ 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(
|
||||||
_,
|
_,
|
||||||
Expression {
|
Expression {
|
||||||
expr: Expr::Call(call),
|
expr: Expr::Call(call),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
) = element
|
) = element
|
||||||
|
{
|
||||||
|
if Some(call.decl_id) == let_decl_id
|
||||||
|
|| Some(call.decl_id) == mut_decl_id
|
||||||
{
|
{
|
||||||
if call.decl_id == let_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),
|
..
|
||||||
..
|
}) = call.positional_iter_mut().nth(1)
|
||||||
}) = call.positional_iter_mut().nth(1)
|
{
|
||||||
|
let block = working_set.get_block(*block_id);
|
||||||
|
|
||||||
|
let element = block.pipelines[0].elements[0].clone();
|
||||||
|
|
||||||
|
if let PipelineElement::Expression(prepend, expr) =
|
||||||
|
element
|
||||||
{
|
{
|
||||||
let block = working_set.get_block(*block_id);
|
if expr.has_in_variable(working_set) {
|
||||||
|
let new_expr = PipelineElement::Expression(
|
||||||
|
prepend,
|
||||||
|
wrap_expr_with_collect(working_set, &expr),
|
||||||
|
);
|
||||||
|
|
||||||
let element =
|
let block =
|
||||||
block.pipelines[0].elements[0].clone();
|
working_set.get_block_mut(*block_id);
|
||||||
|
block.pipelines[0].elements[0] = new_expr;
|
||||||
if let PipelineElement::Expression(prepend, expr) =
|
|
||||||
element
|
|
||||||
{
|
|
||||||
if expr.has_in_variable(working_set) {
|
|
||||||
let new_expr = PipelineElement::Expression(
|
|
||||||
prepend,
|
|
||||||
wrap_expr_with_collect(
|
|
||||||
working_set,
|
|
||||||
&expr,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
let block =
|
|
||||||
working_set.get_block_mut(*block_id);
|
|
||||||
block.pipelines[0].elements[0] = new_expr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
} else if element.has_in_variable(working_set)
|
|
||||||
&& !is_subexpression
|
|
||||||
{
|
|
||||||
*element =
|
|
||||||
wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
}
|
||||||
|
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);
|
*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,49 +5479,50 @@ 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(
|
_,
|
||||||
_,
|
Expression {
|
||||||
Expression {
|
expr: Expr::Call(call),
|
||||||
expr: Expr::Call(call),
|
..
|
||||||
..
|
},
|
||||||
},
|
) = element
|
||||||
) = element
|
{
|
||||||
|
if Some(call.decl_id) == let_decl_id
|
||||||
|
|| Some(call.decl_id) == mut_decl_id
|
||||||
{
|
{
|
||||||
if call.decl_id == let_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),
|
..
|
||||||
..
|
}) = call.positional_iter_mut().nth(1)
|
||||||
}) = call.positional_iter_mut().nth(1)
|
{
|
||||||
{
|
let block = working_set.get_block(*block_id);
|
||||||
let block = working_set.get_block(*block_id);
|
|
||||||
|
|
||||||
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,
|
wrap_expr_with_collect(working_set, &expr),
|
||||||
wrap_expr_with_collect(working_set, &expr),
|
);
|
||||||
);
|
|
||||||
|
|
||||||
let block = working_set.get_block_mut(*block_id);
|
let block = working_set.get_block_mut(*block_id);
|
||||||
block.pipelines[0].elements[0] = new_expr;
|
block.pipelines[0].elements[0] = new_expr;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue;
|
|
||||||
} else if element.has_in_variable(working_set) && !is_subexpression
|
|
||||||
{
|
|
||||||
*element = wrap_element_with_collect(working_set, element);
|
|
||||||
}
|
}
|
||||||
|
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 {
|
||||||
|
*element = wrap_element_with_collect(working_set, element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue