Revert "Pipeline operators: && and ||" (#7452)

Reverts nushell/nushell#7448

Some surprising behavior in how we do this. For example:

```
〉if (true || false) { print "yes!" } else { print "no!" }
no!
〉if (true or false) { print "yes!" } else { print "no!" }
yes!
```

This means for folks who are using the old `||`, they possibly get the
wrong answer once they upgrade. I don't think we can ship with that as
it will catch too many people by surprise and just make it easier to
write buggy code.
This commit is contained in:
JT 2022-12-13 16:36:13 +13:00 committed by GitHub
parent b7a3e5989d
commit 0c656fd276
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 180 additions and 321 deletions

View File

@ -127,6 +127,7 @@ impl NuCompleter {
match pipeline_element { match pipeline_element {
PipelineElement::Expression(_, expr) PipelineElement::Expression(_, expr)
| PipelineElement::Redirection(_, _, expr) | PipelineElement::Redirection(_, _, expr)
| PipelineElement::And(_, expr)
| PipelineElement::Or(_, expr) => { | PipelineElement::Or(_, expr) => {
let flattened: Vec<_> = flatten_expression(&working_set, &expr); let flattened: Vec<_> = flatten_expression(&working_set, &expr);
let span_offset: usize = alias_offset.iter().sum(); let span_offset: usize = alias_offset.iter().sum();

View File

@ -232,6 +232,7 @@ fn find_matching_block_end_in_block(
match e { match e {
PipelineElement::Expression(_, e) PipelineElement::Expression(_, e)
| PipelineElement::Redirection(_, _, e) | PipelineElement::Redirection(_, _, e)
| PipelineElement::And(_, e)
| PipelineElement::Or(_, e) => { | PipelineElement::Or(_, e) => {
if e.span.contains(global_cursor_offset) { if e.span.contains(global_cursor_offset) {
if let Some(pos) = find_matching_block_end_in_expr( if let Some(pos) = find_matching_block_end_in_expr(

View File

@ -146,6 +146,7 @@ impl Command for FromNuon {
match pipeline.elements.remove(0) { match pipeline.elements.remove(0) {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) => expression, | PipelineElement::Or(_, expression) => expression,
} }
} }

View File

@ -789,16 +789,14 @@ pub fn eval_element_with_input(
redirect_stderr: bool, redirect_stderr: bool,
) -> Result<(PipelineData, bool), ShellError> { ) -> Result<(PipelineData, bool), ShellError> {
match element { match element {
PipelineElement::Expression(_, expr) | PipelineElement::Or(_, expr) => { PipelineElement::Expression(_, expr) => eval_expression_with_input(
eval_expression_with_input(
engine_state, engine_state,
stack, stack,
expr, expr,
input, input,
redirect_stdout, redirect_stdout,
redirect_stderr, redirect_stderr,
) ),
}
PipelineElement::Redirection(span, redirection, expr) => match &expr.expr { PipelineElement::Redirection(span, redirection, expr) => match &expr.expr {
Expr::String(_) => { Expr::String(_) => {
let input = match (redirection, input) { let input = match (redirection, input) {
@ -905,6 +903,22 @@ pub fn eval_element_with_input(
} }
_ => Err(ShellError::CommandNotFound(*span)), _ => Err(ShellError::CommandNotFound(*span)),
}, },
PipelineElement::And(_, expr) => eval_expression_with_input(
engine_state,
stack,
expr,
input,
redirect_stdout,
redirect_stderr,
),
PipelineElement::Or(_, expr) => eval_expression_with_input(
engine_state,
stack,
expr,
input,
redirect_stdout,
redirect_stderr,
),
} }
} }
@ -938,16 +952,10 @@ pub fn eval_block(
redirect_stderr: bool, redirect_stderr: bool,
) -> Result<PipelineData, ShellError> { ) -> Result<PipelineData, ShellError> {
let num_pipelines = block.len(); let num_pipelines = block.len();
let mut pipeline_idx = 0; for (pipeline_idx, pipeline) in block.pipelines.iter().enumerate() {
while pipeline_idx < block.pipelines.len() {
let pipeline = &block.pipelines[pipeline_idx];
let mut i = 0; let mut i = 0;
let mut last_element_is_pipeline_or = false; while i < pipeline.elements.len() {
while i < pipeline.elements.len() && !last_element_is_pipeline_or {
let redirect_stderr = redirect_stderr let redirect_stderr = redirect_stderr
|| ((i < pipeline.elements.len() - 1) || ((i < pipeline.elements.len() - 1)
&& (matches!( && (matches!(
@ -956,8 +964,6 @@ pub fn eval_block(
| PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _) | PipelineElement::Redirection(_, Redirection::StdoutAndStderr, _)
))); )));
last_element_is_pipeline_or = matches!(&pipeline.elements[i], PipelineElement::Or(..));
// if eval internal command failed, it can just make early return with `Err(ShellError)`. // if eval internal command failed, it can just make early return with `Err(ShellError)`.
let eval_result = eval_element_with_input( let eval_result = eval_element_with_input(
engine_state, engine_state,
@ -984,16 +990,10 @@ pub fn eval_block(
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message. // don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
} }
(Err(error), true) => input = PipelineData::Value(Value::Error { error }, None), (Err(error), true) => input = PipelineData::Value(Value::Error { error }, None),
(output, false) if last_element_is_pipeline_or => match output {
Ok(output) => {
input = output.0;
}
Err(error) => input = PipelineData::Value(Value::Error { error }, None),
},
(output, false) => { (output, false) => {
let output = output?; let output = output?;
input = output.0; input = output.0;
// external command may have failed // external command may runs to failed
// make early return so remaining commands will not be executed. // make early return so remaining commands will not be executed.
// don't return `Err(ShellError)`, so nushell wouldn't show extra error message. // don't return `Err(ShellError)`, so nushell wouldn't show extra error message.
if output.1 { if output.1 {
@ -1006,40 +1006,6 @@ pub fn eval_block(
} }
if pipeline_idx < (num_pipelines) - 1 { if pipeline_idx < (num_pipelines) - 1 {
if last_element_is_pipeline_or {
let input_is_error = matches!(input, PipelineData::Value(Value::Error { .. }, ..));
let result =
drain_and_print(engine_state, stack, input, redirect_stdout, redirect_stderr);
let last_exit_code = stack.last_exit_code(engine_state).unwrap_or(0);
if last_exit_code == 0 && result.is_ok() && !input_is_error {
// Skip the next pipeline ot run because this pipeline was successful and the
// user used the `a || b` connector
pipeline_idx += 1;
}
input = PipelineData::empty()
} else {
drain_and_print(engine_state, stack, input, redirect_stdout, redirect_stderr)?;
input = PipelineData::empty()
}
}
pipeline_idx += 1;
}
Ok(input)
}
fn drain_and_print(
engine_state: &EngineState,
stack: &mut Stack,
mut input: PipelineData,
redirect_stdout: bool,
redirect_stderr: bool,
) -> Result<(), ShellError> {
match input { match input {
PipelineData::Value(Value::Nothing { .. }, ..) => {} PipelineData::Value(Value::Nothing { .. }, ..) => {}
PipelineData::ExternalStream { PipelineData::ExternalStream {
@ -1093,8 +1059,12 @@ fn drain_and_print(
redirect_stderr, redirect_stderr,
)?; )?;
} else { } else {
let table = let table = table.run(
table.run(engine_state, stack, &Call::new(Span::new(0, 0)), input)?; engine_state,
stack,
&Call::new(Span::new(0, 0)),
input,
)?;
print_or_return(table, config)?; print_or_return(table, config)?;
} }
@ -1106,7 +1076,11 @@ fn drain_and_print(
} }
} }
Ok(()) input = PipelineData::empty()
}
}
Ok(input)
} }
fn print_or_return(pipeline_data: PipelineData, config: &Config) -> Result<(), ShellError> { fn print_or_return(pipeline_data: PipelineData, config: &Config) -> Result<(), ShellError> {

View File

@ -455,10 +455,14 @@ pub fn flatten_pipeline_element(
output.append(&mut flatten_expression(working_set, expr)); output.append(&mut flatten_expression(working_set, expr));
output output
} }
PipelineElement::Or(span, expr) => { PipelineElement::And(span, expr) => {
let mut output = vec![]; let mut output = vec![(*span, FlatShape::And)];
output.append(&mut flatten_expression(working_set, expr));
output
}
PipelineElement::Or(span, expr) => {
let mut output = vec![(*span, FlatShape::Or)];
output.append(&mut flatten_expression(working_set, expr)); output.append(&mut flatten_expression(working_set, expr));
output.push((*span, FlatShape::Or));
output output
} }
} }

View File

@ -7,7 +7,6 @@ pub enum TokenContents {
Comment, Comment,
Pipe, Pipe,
PipePipe, PipePipe,
AndAnd,
Semicolon, Semicolon,
OutGreaterThan, OutGreaterThan,
ErrGreaterThan, ErrGreaterThan,
@ -255,10 +254,10 @@ pub fn lex_item(
), ),
b"&&" => ( b"&&" => (
Token { Token {
contents: TokenContents::AndAnd, contents: TokenContents::Item,
span, span,
}, },
None, Some(ParseError::ShellAndAnd(span)),
), ),
b"2>" => ( b"2>" => (
Token { Token {

View File

@ -1420,9 +1420,7 @@ pub fn parse_module_block(
pipeline pipeline
} }
LiteElement::Redirection(_, _, command) | LiteElement::Or(_, command) => { LiteElement::Redirection(_, _, command) => garbage_pipeline(&command.parts),
garbage_pipeline(&command.parts)
}
} }
} else { } else {
error = Some(ParseError::Expected("not a pipeline".into(), span)); error = Some(ParseError::Expected("not a pipeline".into(), span));

View File

@ -1223,7 +1223,6 @@ fn parse_binary_with_base(
} }
TokenContents::Pipe TokenContents::Pipe
| TokenContents::PipePipe | TokenContents::PipePipe
| TokenContents::AndAnd
| TokenContents::OutGreaterThan | TokenContents::OutGreaterThan
| TokenContents::ErrGreaterThan | TokenContents::ErrGreaterThan
| TokenContents::OutErrGreaterThan => { | TokenContents::OutErrGreaterThan => {
@ -3805,9 +3804,7 @@ pub fn parse_table_expression(
} }
_ => { _ => {
match &output.block[0].commands[0] { match &output.block[0].commands[0] {
LiteElement::Command(_, command) LiteElement::Command(_, command) | LiteElement::Redirection(_, _, command) => {
| LiteElement::Redirection(_, _, command)
| LiteElement::Or(_, command) => {
let mut table_headers = vec![]; let mut table_headers = vec![];
let (headers, err) = parse_value( let (headers, err) = parse_value(
@ -3828,8 +3825,7 @@ pub fn parse_table_expression(
match &output.block[1].commands[0] { match &output.block[1].commands[0] {
LiteElement::Command(_, command) LiteElement::Command(_, command)
| LiteElement::Redirection(_, _, command) | LiteElement::Redirection(_, _, command) => {
| LiteElement::Or(_, command) => {
let mut rows = vec![]; let mut rows = vec![];
for part in &command.parts { for part in &command.parts {
let (values, err) = parse_value( let (values, err) = parse_value(
@ -5286,9 +5282,7 @@ pub fn parse_block(
for pipeline in &lite_block.block { for pipeline in &lite_block.block {
if pipeline.commands.len() == 1 { if pipeline.commands.len() == 1 {
match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(_, command) LiteElement::Command(_, command) | LiteElement::Redirection(_, _, command) => {
| LiteElement::Redirection(_, _, command)
| LiteElement::Or(_, command) => {
if let Some(err) = if let Some(err) =
parse_def_predecl(working_set, &command.parts, expand_aliases_denylist) parse_def_predecl(working_set, &command.parts, expand_aliases_denylist)
{ {
@ -5325,21 +5319,6 @@ pub fn parse_block(
PipelineElement::Expression(*span, expr) PipelineElement::Expression(*span, expr)
} }
LiteElement::Or(span, command) => {
let (expr, err) = parse_expression(
working_set,
&command.parts,
expand_aliases_denylist,
is_subexpression,
);
working_set.type_scope.add_type(expr.ty.clone());
if error.is_none() {
error = err;
}
PipelineElement::Or(*span, expr)
}
LiteElement::Redirection(span, redirection, command) => { LiteElement::Redirection(span, redirection, command) => {
trace!("parsing: pipeline element: redirection"); trace!("parsing: pipeline element: redirection");
let (expr, err) = parse_string( let (expr, err) = parse_string(
@ -5375,16 +5354,8 @@ pub fn parse_block(
Pipeline { elements: output } Pipeline { elements: output }
} else { } else {
let (mut pipeline, err) = match &pipeline.commands[0] { match &pipeline.commands[0] {
LiteElement::Command(_, command) | LiteElement::Redirection(_, _, command) => { LiteElement::Command(_, command) | LiteElement::Redirection(_, _, command) => {
parse_builtin_commands(
working_set,
command,
expand_aliases_denylist,
is_subexpression,
)
}
LiteElement::Or(span, command) => {
let (mut pipeline, err) = parse_builtin_commands( let (mut pipeline, err) = parse_builtin_commands(
working_set, working_set,
command, command,
@ -5392,17 +5363,10 @@ pub fn parse_block(
is_subexpression, is_subexpression,
); );
if let PipelineElement::Expression(_, expr) = &pipeline.elements[0] {
pipeline.elements[0] = PipelineElement::Or(*span, expr.clone())
}
(pipeline, err)
}
};
if idx == 0 { if idx == 0 {
if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Any) { if let Some(let_decl_id) = working_set.find_decl(b"let", &Type::Any) {
if let Some(let_env_decl_id) = working_set.find_decl(b"let-env", &Type::Any) if let Some(let_env_decl_id) =
working_set.find_decl(b"let-env", &Type::Any)
{ {
for element in pipeline.elements.iter_mut() { for element in pipeline.elements.iter_mut() {
if let PipelineElement::Expression( if let PipelineElement::Expression(
@ -5433,11 +5397,14 @@ pub fn parse_block(
} 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 } 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);
} }
} }
} }
@ -5450,6 +5417,8 @@ pub fn parse_block(
pipeline pipeline
} }
}
}
}) })
.into(); .into();
@ -5525,6 +5494,7 @@ pub fn discover_captures_in_pipeline_element(
match element { match element {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) => { | PipelineElement::Or(_, expression) => {
discover_captures_in_expr(working_set, expression, seen, seen_blocks) discover_captures_in_expr(working_set, expression, seen, seen_blocks)
} }
@ -5785,6 +5755,9 @@ fn wrap_element_with_collect(
wrap_expr_with_collect(working_set, expression), wrap_expr_with_collect(working_set, expression),
) )
} }
PipelineElement::And(span, expression) => {
PipelineElement::And(*span, wrap_expr_with_collect(working_set, expression))
}
PipelineElement::Or(span, expression) => { PipelineElement::Or(span, expression) => {
PipelineElement::Or(*span, wrap_expr_with_collect(working_set, expression)) PipelineElement::Or(*span, wrap_expr_with_collect(working_set, expression))
} }
@ -5888,7 +5861,6 @@ impl LiteCommand {
pub enum LiteElement { pub enum LiteElement {
Command(Option<Span>, LiteCommand), Command(Option<Span>, LiteCommand),
Redirection(Span, Redirection, LiteCommand), Redirection(Span, Redirection, LiteCommand),
Or(Span, LiteCommand),
} }
#[derive(Debug)] #[derive(Debug)]
@ -5957,8 +5929,15 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
let mut curr_comment: Option<Vec<Span>> = None; let mut curr_comment: Option<Vec<Span>> = None;
let mut error = None;
for token in tokens.iter() { for token in tokens.iter() {
match &token.contents { match &token.contents {
TokenContents::PipePipe => {
error = error.or(Some(ParseError::ShellOrOr(token.span)));
curr_command.push(token.span);
last_token = TokenContents::Item;
}
TokenContents::Item => { TokenContents::Item => {
// If we have a comment, go ahead and attach it // If we have a comment, go ahead and attach it
if let Some(curr_comment) = curr_comment.take() { if let Some(curr_comment) = curr_comment.take() {
@ -6094,25 +6073,7 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
last_token = TokenContents::Eol; last_token = TokenContents::Eol;
} }
TokenContents::PipePipe => { TokenContents::Semicolon => {
// Different to redirection, for PipePipe, we'll wrap the current command
// in an "Or". This lets us know during eval that it's the current command
// whose error will be ignored rather than having to look ahead in the pipeline
if !curr_command.is_empty() {
curr_pipeline.push(LiteElement::Or(token.span, curr_command));
curr_command = LiteCommand::new();
}
if !curr_pipeline.is_empty() {
block.push(curr_pipeline);
curr_pipeline = LitePipeline::new();
last_connector = TokenContents::Pipe;
last_connector_span = None;
}
last_token = TokenContents::PipePipe;
}
TokenContents::Semicolon | TokenContents::AndAnd => {
if !curr_command.is_empty() { if !curr_command.is_empty() {
match last_connector { match last_connector {
TokenContents::OutGreaterThan => { TokenContents::OutGreaterThan => {
@ -6141,7 +6102,7 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
} }
_ => { _ => {
curr_pipeline curr_pipeline
.push(LiteElement::Command(Some(token.span), curr_command)); .push(LiteElement::Command(last_connector_span, curr_command));
} }
} }
@ -6213,10 +6174,7 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
block.push(curr_pipeline); block.push(curr_pipeline);
} }
if last_token == TokenContents::Pipe if last_token == TokenContents::Pipe {
|| last_token == TokenContents::PipePipe
|| last_token == TokenContents::AndAnd
{
( (
block, block,
Some(ParseError::UnexpectedEof( Some(ParseError::UnexpectedEof(
@ -6225,7 +6183,7 @@ pub fn lite_parse(tokens: &[Token]) -> (LiteBlock, Option<ParseError>) {
)), )),
) )
} else { } else {
(block, None) (block, error)
} }
} }

View File

@ -14,6 +14,7 @@ pub enum Redirection {
pub enum PipelineElement { pub enum PipelineElement {
Expression(Option<Span>, Expression), Expression(Option<Span>, Expression),
Redirection(Span, Redirection, Expression), Redirection(Span, Redirection, Expression),
And(Span, Expression),
Or(Span, Expression), Or(Span, Expression),
} }
@ -22,8 +23,9 @@ impl PipelineElement {
match self { match self {
PipelineElement::Expression(None, expression) => expression.span, PipelineElement::Expression(None, expression) => expression.span,
PipelineElement::Expression(Some(span), expression) PipelineElement::Expression(Some(span), expression)
| PipelineElement::Or(span, expression) | PipelineElement::Redirection(span, _, expression)
| PipelineElement::Redirection(span, _, expression) => Span { | PipelineElement::And(span, expression)
| PipelineElement::Or(span, expression) => Span {
start: span.start, start: span.start,
end: expression.span.end, end: expression.span.end,
}, },
@ -33,6 +35,7 @@ impl PipelineElement {
match self { match self {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Redirection(_, _, expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) => expression.has_in_variable(working_set), | PipelineElement::Or(_, expression) => expression.has_in_variable(working_set),
} }
} }
@ -40,8 +43,9 @@ impl PipelineElement {
pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) { pub fn replace_in_variable(&mut self, working_set: &mut StateWorkingSet, new_var_id: VarId) {
match self { match self {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Or(_, expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::Redirection(_, _, expression) => { | PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) => {
expression.replace_in_variable(working_set, new_var_id) expression.replace_in_variable(working_set, new_var_id)
} }
} }
@ -55,8 +59,9 @@ impl PipelineElement {
) { ) {
match self { match self {
PipelineElement::Expression(_, expression) PipelineElement::Expression(_, expression)
| PipelineElement::Or(_, expression) | PipelineElement::Redirection(_, _, expression)
| PipelineElement::Redirection(_, _, expression) => { | PipelineElement::And(_, expression)
| PipelineElement::Or(_, expression) => {
expression.replace_span(working_set, replaced, new_span) expression.replace_span(working_set, replaced, new_span)
} }
} }

View File

@ -113,13 +113,6 @@ impl Stack {
.ok_or_else(|| ShellError::NushellFailed("No active overlay".into())) .ok_or_else(|| ShellError::NushellFailed("No active overlay".into()))
} }
pub fn last_exit_code(&self, engine_state: &EngineState) -> Option<i64> {
match self.get_env_var(engine_state, "LAST_EXIT_CODE") {
Some(Value::Int { val, .. }) => Some(val),
_ => None,
}
}
pub fn captures_to_stack(&self, captures: &HashMap<VarId, Value>) -> Stack { pub fn captures_to_stack(&self, captures: &HashMap<VarId, Value>) -> Stack {
// FIXME: this is probably slow // FIXME: this is probably slow
let mut env_vars = self.env_vars.clone(); let mut env_vars = self.env_vars.clone();

View File

@ -1,5 +1,4 @@
mod commands; mod commands;
mod pipeline_operators;
use nu_test_support::nu; use nu_test_support::nu;

View File

@ -1,74 +0,0 @@
use nu_test_support::nu;
#[test]
fn and_operator1() {
let actual = nu!(
cwd: ".",
r#"
3 / 0 && 2 + 3
"#
);
assert!(actual.err.contains("division by zero"));
}
#[test]
fn and_operator2() {
let actual = nu!(
cwd: ".",
r#"
0 | 3 / $in && 2 + 3
"#
);
assert!(actual.err.contains("division by zero"));
}
#[test]
fn or_operator1() {
let actual = nu!(
cwd: ".",
r#"
3 / 0 || 2 + 3
"#
);
assert!(actual.out.contains('5'));
}
#[test]
fn or_operator2() {
let actual = nu!(
cwd: ".",
r#"
0 | 3 / $in || 2 + 3
"#
);
assert!(actual.out.contains('5'));
}
#[test]
fn or_operator3() {
// On success, don't run the next step
let actual = nu!(
cwd: ".",
r#"
0 || 2 + 3
"#
);
assert_eq!(actual.out, "0");
}
#[test]
fn or_operator4() {
let actual = nu!(
cwd: ".",
r#"
1 / 0 || 2 / 0 || 10 + 9
"#
);
assert!(actual.out.contains("19"));
}