Let with pipeline (#9589)

# Description

This changes the default behaviour of `let` to be able to take a
pipeline as its initial value.

For example:

```
> let x = "hello world" | str length
```

This is a change from the existing behaviour, where the right hand side
is assumed to be an expression. Pipelines are more general, and can be
more powerful.

My google foo is failing me, but this also fixes this issue:

```
let x = foo
```

Currently, this reads `foo` as a bareword that gets converted to a
string rather than running the `foo` command. In practice, this is
really annoying and is a really hard to spot bug in a script.

# User-Facing Changes

BREAKING CHANGE BREAKING CHANGE

`let` gains the power to be assigned via a pipeline. However, this
changes the behaviour of `let x = foo` from assigning the string "foo"
to `$x` to being "run the command `foo` and give the result to `$x`"

# 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-03 17:45:10 +12:00
committed by GitHub
parent b70cce47e2
commit 5d9e2455f7
7 changed files with 522 additions and 291 deletions

View File

@@ -1,4 +1,4 @@
use crate::{parser_path::ParserPath, type_check::type_compatible};
use crate::{parse_block, parser_path::ParserPath, type_check::type_compatible};
use itertools::Itertools;
use log::trace;
use nu_path::canonicalize_with;
@@ -2795,128 +2795,234 @@ pub fn parse_overlay_hide(working_set: &mut StateWorkingSet, call: Box<Call>) ->
pipeline
}
pub fn parse_let_or_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
pub fn parse_let(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
trace!("parsing: let");
let name = working_set.get_span_contents(spans[0]);
if name == b"let" || name == b"const" {
let is_const = &name == b"const";
// JT: Disabling check_name because it doesn't work with optional types in the declaration
// if let Some(span) = check_name(working_set, spans) {
// return Pipeline::from_vec(vec![garbage(*span)]);
// }
// JT: Disabling check_name because it doesn't work with optional types in the declaration
// if let Some(span) = check_name(working_set, spans) {
// return Pipeline::from_vec(vec![garbage(*span)]);
// }
if let Some(decl_id) = working_set.find_decl(b"let", &Type::Nothing) {
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
// 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 let Some(decl_id) =
working_set.find_decl(if is_const { b"const" } else { b"let" }, &Type::Nothing)
{
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
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
// 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,
false,
);
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);
}
if is_const {
match eval_constant(working_set, &rvalue) {
Ok(val) => {
working_set.add_constant(var_id, val);
}
Err(err) => working_set.error(err),
}
}
}
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,
}]);
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, false);
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,
}]);
} else {
working_set.error(ParseError::UnknownState(
"internal error: let or const statements not found in core language".into(),
span(spans),
))
}
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(
"internal error: let or const statement unparsable".into(),
span(spans),
));
garbage_pipeline(spans)
}
pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
trace!("parsing: const");
// JT: Disabling check_name because it doesn't work with optional types in the declaration
// if let Some(span) = check_name(working_set, spans) {
// return Pipeline::from_vec(vec![garbage(*span)]);
// }
if let Some(decl_id) = working_set.find_decl(b"const", &Type::Nothing) {
let cmd = working_set.get_decl(decl_id);
let call_signature = cmd.signature().call_signature();
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
// 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),
// ),
// );
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, false);
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);
}
match eval_constant(working_set, &rvalue) {
Ok(val) => {
working_set.add_constant(var_id, val);
}
Err(err) => working_set.error(err),
}
}
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,
}]);
} 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(