From 6b4282eadfdbb89626d8d461d1ac01ef9c248068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20=C5=BD=C3=A1dn=C3=ADk?= Date: Sat, 10 Dec 2022 19:23:24 +0200 Subject: [PATCH] Move 'where' to parser keywords; Add 'filter' command (#7365) # Description This PR moves the `where` command to a parser keyword. While it still uses the shape-directed parsing dictated by the signature, we're free to change the parsing code now to a custom one once we remove the syntax shapes. As a side effect, the `where -b` flag was removed and its functionality has moved to the new `filter` command. Just FYI, other commands that take row conditions: - `take until` - `take while` - `skip until` - `skip while` - `any` - `all` We can either move these to the parser as well or make them accept a closure instead of row condition. # User-Facing Changes New `filter` command which replaces `where -b` functionality. # 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` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # 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. --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/filters/filter.rs | 282 ++++++++++++++++++ crates/nu-command/src/filters/mod.rs | 2 + crates/nu-command/src/filters/where_.rs | 326 ++++++--------------- crates/nu-command/tests/commands/where_.rs | 10 + crates/nu-parser/src/parse_keywords.rs | 92 ++++++ crates/nu-parser/src/parser.rs | 18 +- crates/nu-protocol/src/shell_error.rs | 13 + 8 files changed, 499 insertions(+), 245 deletions(-) create mode 100644 crates/nu-command/src/filters/filter.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 6dd0f6e2e6..0a92fb879f 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -92,6 +92,7 @@ pub fn create_default_context() -> EngineState { EachWhile, Empty, Every, + Filter, Find, First, Flatten, diff --git a/crates/nu-command/src/filters/filter.rs b/crates/nu-command/src/filters/filter.rs new file mode 100644 index 0000000000..ae9d471106 --- /dev/null +++ b/crates/nu-command/src/filters/filter.rs @@ -0,0 +1,282 @@ +use super::utils::chain_error_with_input; +use nu_engine::{eval_block, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Closure, Command, EngineState, Stack}; +use nu_protocol::{ + Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, Signature, + Span, SyntaxShape, Type, Value, +}; + +#[derive(Clone)] +pub struct Filter; + +impl Command for Filter { + fn name(&self) -> &str { + "filter" + } + + fn usage(&self) -> &str { + "Filter values based on a predicate closure." + } + + fn extra_usage(&self) -> &str { + r#"This command works similar to 'where' but allows reading the predicate closure from +a variable. On the other hand, the "row condition" syntax is not supported."# + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("filter") + .input_output_types(vec![ + ( + Type::List(Box::new(Type::Any)), + Type::List(Box::new(Type::Any)), + ), + (Type::Table(vec![]), Type::Table(vec![])), + ]) + .required( + "closure", + SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + "Predicate closure", + ) + .category(Category::Filters) + } + + fn search_terms(&self) -> Vec<&str> { + vec!["where", "find", "search", "condition"] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let capture_block: Closure = call.req(engine_state, stack, 0)?; + let metadata = input.metadata(); + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + let block = engine_state.get_block(capture_block.block_id).clone(); + let mut stack = stack.captures_to_stack(&capture_block.captures); + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); + let span = call.head; + let redirect_stdout = call.redirect_stdout; + let redirect_stderr = call.redirect_stderr; + + match input { + PipelineData::Empty => Ok(PipelineData::Empty), + PipelineData::Value(Value::Range { .. }, ..) + | PipelineData::Value(Value::List { .. }, ..) + | PipelineData::ListStream { .. } => Ok(input + // To enumerate over the input (for the index argument), + // it must be converted into an iterator using into_iter(). + .into_iter() + .enumerate() + .filter_map(move |(idx, x)| { + // with_env() is used here to ensure that each iteration uses + // a different set of environment variables. + // Hence, a 'cd' in the first loop won't affect the next loop. + stack.with_env(&orig_env_vars, &orig_env_hidden); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, x.clone()); + } + } + // Optional index argument + if let Some(var) = block.signature.get_positional(1) { + if let Some(var_id) = &var.var_id { + stack.add_var( + *var_id, + Value::Int { + val: idx as i64, + span, + }, + ); + } + } + + match eval_block( + &engine_state, + &mut stack, + &block, + // clone() is used here because x is given to Ok() below. + x.clone().into_pipeline_data(), + redirect_stdout, + redirect_stderr, + ) { + Ok(v) => { + if v.into_value(span).is_true() { + Some(x) + } else { + None + } + } + Err(error) => Some(Value::Error { + error: chain_error_with_input(error, x.span()), + }), + } + }) + .into_pipeline_data(ctrlc)), + PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()), + PipelineData::ExternalStream { + stdout: Some(stream), + .. + } => Ok(stream + .into_iter() + .enumerate() + .filter_map(move |(idx, x)| { + // see note above about with_env() + stack.with_env(&orig_env_vars, &orig_env_hidden); + + let x = match x { + Ok(x) => x, + Err(err) => return Some(Value::Error { error: err }), + }; + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, x.clone()); + } + } + // Optional index argument + if let Some(var) = block.signature.get_positional(1) { + if let Some(var_id) = &var.var_id { + stack.add_var( + *var_id, + Value::Int { + val: idx as i64, + span, + }, + ); + } + } + + match eval_block( + &engine_state, + &mut stack, + &block, + // clone() is used here because x is given to Ok() below. + x.clone().into_pipeline_data(), + redirect_stdout, + redirect_stderr, + ) { + Ok(v) => { + if v.into_value(span).is_true() { + Some(x) + } else { + None + } + } + Err(error) => Some(Value::Error { + error: chain_error_with_input(error, x.span()), + }), + } + }) + .into_pipeline_data(ctrlc)), + // This match allows non-iterables to be accepted, + // which is currently considered undesirable (Nov 2022). + PipelineData::Value(x, ..) => { + // see note above about with_env() + stack.with_env(&orig_env_vars, &orig_env_hidden); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, x.clone()); + } + } + Ok(match eval_block( + &engine_state, + &mut stack, + &block, + // clone() is used here because x is given to Ok() below. + x.clone().into_pipeline_data(), + redirect_stdout, + redirect_stderr, + ) { + Ok(v) => { + if v.into_value(span).is_true() { + Some(x) + } else { + None + } + } + Err(error) => Some(Value::Error { + error: chain_error_with_input(error, x.span()), + }), + } + .into_pipeline_data(ctrlc)) + } + } + .map(|x| x.set_metadata(metadata)) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Filter items of a list according to a condition", + example: "[1 2] | filter {|x| $x > 1}", + result: Some(Value::List { + vals: vec![Value::test_int(2)], + span: Span::test_data(), + }), + }, + Example { + description: "Filter rows of a table according to a condition", + example: "[{a: 1} {a: 2}] | filter {|x| $x.a > 1}", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["a".to_string()], + vals: vec![Value::test_int(2)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + Example { + description: "Filter rows of a table according to a stored condition", + example: "let cond = {|x| $x.a > 1}; [{a: 1} {a: 2}] | filter $cond", + result: Some(Value::List { + vals: vec![Value::Record { + cols: vec!["a".to_string()], + vals: vec![Value::test_int(2)], + span: Span::test_data(), + }], + span: Span::test_data(), + }), + }, + // TODO: This should work but does not. (Note that `Let` must be present in the working_set in `example_test.rs`). + // See https://github.com/nushell/nushell/issues/7034 + // Example { + // description: "List all numbers above 3, using an existing closure condition", + // example: "let a = {$in > 3}; [1, 2, 5, 6] | filter $a", + // result: Some(Value::List { + // vals: vec![ + // Value::Int { + // val: 5, + // span: Span::test_data(), + // }, + // Value::Int { + // val: 6, + // span: Span::test_data(), + // }, + // ], + // span: Span::test_data(), + // }), + // }, + ] + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(Filter {}) + } +} diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 7aac8d7d54..177c3bc8b4 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -10,6 +10,7 @@ mod each; mod each_while; mod empty; mod every; +mod filter; mod find; mod first; mod flatten; @@ -63,6 +64,7 @@ pub use each::Each; pub use each_while::EachWhile; pub use empty::Empty; pub use every::Every; +pub use filter::Filter; pub use find::Find; pub use first::First; pub use flatten::Flatten; diff --git a/crates/nu-command/src/filters/where_.rs b/crates/nu-command/src/filters/where_.rs index 81cc6b4dad..d7ec659567 100644 --- a/crates/nu-command/src/filters/where_.rs +++ b/crates/nu-command/src/filters/where_.rs @@ -1,10 +1,9 @@ -use super::utils::chain_error_with_input; use nu_engine::{eval_block, CallExt}; use nu_protocol::ast::Call; use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, - Signature, Span, SyntaxShape, Type, Value, + Signature, Span, Spanned, SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -16,7 +15,13 @@ impl Command for Where { } fn usage(&self) -> &str { - "Filter values based on a condition." + "Filter values based on a row condition." + } + + fn extra_usage(&self) -> &str { + r#"This command works similar to 'filter' but allows extra shorthands for working with +tables, known as "row conditions". On the other hand, reading the condition from a variable is +not supported."# } fn signature(&self) -> nu_protocol::Signature { @@ -28,11 +33,16 @@ impl Command for Where { ), (Type::Table(vec![]), Type::Table(vec![])), ]) - .optional("cond", SyntaxShape::RowCondition, "condition") + .required( + "row_condition", + SyntaxShape::RowCondition, + "Filter condition", + ) + // TODO: Remove this flag after 0.73.0 release .named( "closure", SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), - "use with a closure instead", + "use with a closure instead (deprecated: use 'filter' command instead)", Some('b'), ) .category(Category::Filters) @@ -49,235 +59,77 @@ impl Command for Where { call: &Call, input: PipelineData, ) -> Result { - if let Ok(Some(capture_block)) = call.get_flag::(engine_state, stack, "closure") { - let metadata = input.metadata(); - let ctrlc = engine_state.ctrlc.clone(); - let engine_state = engine_state.clone(); - let block = engine_state.get_block(capture_block.block_id).clone(); - let mut stack = stack.captures_to_stack(&capture_block.captures); - let orig_env_vars = stack.env_vars.clone(); - let orig_env_hidden = stack.env_hidden.clone(); - let span = call.head; - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - - match input { - PipelineData::Empty => Ok(PipelineData::Empty), - PipelineData::Value(Value::Range { .. }, ..) - | PipelineData::Value(Value::List { .. }, ..) - | PipelineData::ListStream { .. } => Ok(input - // To enumerate over the input (for the index argument), - // it must be converted into an iterator using into_iter(). - .into_iter() - .enumerate() - .filter_map(move |(idx, x)| { - // with_env() is used here to ensure that each iteration uses - // a different set of environment variables. - // Hence, a 'cd' in the first loop won't affect the next loop. - stack.with_env(&orig_env_vars, &orig_env_hidden); - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, x.clone()); - } - } - // Optional index argument - if let Some(var) = block.signature.get_positional(1) { - if let Some(var_id) = &var.var_id { - stack.add_var( - *var_id, - Value::Int { - val: idx as i64, - span, - }, - ); - } - } - - match eval_block( - &engine_state, - &mut stack, - &block, - // clone() is used here because x is given to Ok() below. - x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, - ) { - Ok(v) => { - if v.into_value(span).is_true() { - Some(x) - } else { - None - } - } - Err(error) => Some(Value::Error { - error: chain_error_with_input(error, x.span()), - }), - } - }) - .into_pipeline_data(ctrlc)), - PipelineData::ExternalStream { stdout: None, .. } => Ok(PipelineData::empty()), - PipelineData::ExternalStream { - stdout: Some(stream), - .. - } => Ok(stream - .into_iter() - .enumerate() - .filter_map(move |(idx, x)| { - // see note above about with_env() - stack.with_env(&orig_env_vars, &orig_env_hidden); - - let x = match x { - Ok(x) => x, - Err(err) => return Some(Value::Error { error: err }), - }; - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, x.clone()); - } - } - // Optional index argument - if let Some(var) = block.signature.get_positional(1) { - if let Some(var_id) = &var.var_id { - stack.add_var( - *var_id, - Value::Int { - val: idx as i64, - span, - }, - ); - } - } - - match eval_block( - &engine_state, - &mut stack, - &block, - // clone() is used here because x is given to Ok() below. - x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, - ) { - Ok(v) => { - if v.into_value(span).is_true() { - Some(x) - } else { - None - } - } - Err(error) => Some(Value::Error { - error: chain_error_with_input(error, x.span()), - }), - } - }) - .into_pipeline_data(ctrlc)), - // This match allows non-iterables to be accepted, - // which is currently considered undesirable (Nov 2022). - PipelineData::Value(x, ..) => { - // see note above about with_env() - stack.with_env(&orig_env_vars, &orig_env_hidden); - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, x.clone()); - } - } - Ok(match eval_block( - &engine_state, - &mut stack, - &block, - // clone() is used here because x is given to Ok() below. - x.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, - ) { - Ok(v) => { - if v.into_value(span).is_true() { - Some(x) - } else { - None - } - } - Err(error) => Some(Value::Error { - error: chain_error_with_input(error, x.span()), - }), - } - .into_pipeline_data(ctrlc)) - } - } - .map(|x| x.set_metadata(metadata)) - } else { - let capture_block: Option = call.opt(engine_state, stack, 0)?; - if let Some(block) = capture_block { - let span = call.head; - - let metadata = input.metadata(); - let mut stack = stack.captures_to_stack(&block.captures); - let block = engine_state.get_block(block.block_id).clone(); - - let orig_env_vars = stack.env_vars.clone(); - let orig_env_hidden = stack.env_hidden.clone(); - - let ctrlc = engine_state.ctrlc.clone(); - let engine_state = engine_state.clone(); - - let redirect_stdout = call.redirect_stdout; - let redirect_stderr = call.redirect_stderr; - Ok(input - .into_iter() - .enumerate() - .filter_map(move |(idx, value)| { - stack.with_env(&orig_env_vars, &orig_env_hidden); - - if let Some(var) = block.signature.get_positional(0) { - if let Some(var_id) = &var.var_id { - stack.add_var(*var_id, value.clone()); - } - } - // Optional index argument - if let Some(var) = block.signature.get_positional(1) { - if let Some(var_id) = &var.var_id { - stack.add_var( - *var_id, - Value::Int { - val: idx as i64, - span, - }, - ); - } - } - let result = eval_block( - &engine_state, - &mut stack, - &block, - // clone() is used here because x is given to Ok() below. - value.clone().into_pipeline_data(), - redirect_stdout, - redirect_stderr, - ); - - match result { - Ok(result) => { - let result = result.into_value(span); - if result.is_true() { - Some(value) - } else { - None - } - } - Err(err) => Some(Value::Error { error: err }), - } - }) - .into_pipeline_data(ctrlc)) - .map(|x| x.set_metadata(metadata)) - } else { - Err(ShellError::MissingParameter( - "condition".to_string(), - call.head, - )) - } + if let Some(closure) = call.get_flag::>(engine_state, stack, "closure")? { + return Err(ShellError::DeprecatedParameter( + "-b, --closure".to_string(), + "filter command".to_string(), + closure.span, + )); } + + let closure: Closure = call.req(engine_state, stack, 0)?; + + let span = call.head; + + let metadata = input.metadata(); + let mut stack = stack.captures_to_stack(&closure.captures); + let block = engine_state.get_block(closure.block_id).clone(); + + let orig_env_vars = stack.env_vars.clone(); + let orig_env_hidden = stack.env_hidden.clone(); + + let ctrlc = engine_state.ctrlc.clone(); + let engine_state = engine_state.clone(); + + let redirect_stdout = call.redirect_stdout; + let redirect_stderr = call.redirect_stderr; + Ok(input + .into_iter() + .enumerate() + .filter_map(move |(idx, value)| { + stack.with_env(&orig_env_vars, &orig_env_hidden); + + if let Some(var) = block.signature.get_positional(0) { + if let Some(var_id) = &var.var_id { + stack.add_var(*var_id, value.clone()); + } + } + // Optional index argument + if let Some(var) = block.signature.get_positional(1) { + if let Some(var_id) = &var.var_id { + stack.add_var( + *var_id, + Value::Int { + val: idx as i64, + span, + }, + ); + } + } + let result = eval_block( + &engine_state, + &mut stack, + &block, + // clone() is used here because x is given to Ok() below. + value.clone().into_pipeline_data(), + redirect_stdout, + redirect_stderr, + ); + + match result { + Ok(result) => { + let result = result.into_value(span); + if result.is_true() { + Some(value) + } else { + None + } + } + Err(err) => Some(Value::Error { error: err }), + } + }) + .into_pipeline_data(ctrlc)) + .map(|x| x.set_metadata(metadata)) } fn examples(&self) -> Vec { @@ -302,14 +154,6 @@ impl Command for Where { span: Span::test_data(), }), }, - Example { - description: "Filter items of a list according to a stored condition", - example: "let cond = {|x| $x > 1}; [1 2] | where -b $cond", - result: Some(Value::List { - vals: vec![Value::test_int(2)], - span: Span::test_data(), - }), - }, Example { description: "List all files in the current directory with sizes greater than 2kb", example: "ls | where size > 2kb", diff --git a/crates/nu-command/tests/commands/where_.rs b/crates/nu-command/tests/commands/where_.rs index 36cce654d0..2cd5609c8d 100644 --- a/crates/nu-command/tests/commands/where_.rs +++ b/crates/nu-command/tests/commands/where_.rs @@ -22,6 +22,16 @@ fn filters_with_nothing_comparison() { assert_eq!(actual.out, "7"); } +#[test] +fn where_inside_block_works() { + let actual = nu!( + cwd: ".", + "{|x| ls | where $it =~ 'foo' } | describe" + ); + + assert_eq!(actual.out, "closure"); +} + #[test] fn filters_with_0_arity_block() { let actual = nu!( diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index e2a837340d..2241b13c63 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -1,3 +1,4 @@ +use log::trace; use nu_path::canonicalize_with; use nu_protocol::{ ast::{ @@ -3130,6 +3131,97 @@ pub fn parse_source( ) } +pub fn parse_where_expr( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Expression, Option) { + trace!("parsing: where"); + + if !spans.is_empty() && working_set.get_span_contents(spans[0]) != b"where" { + return ( + garbage(span(spans)), + Some(ParseError::UnknownState( + "internal error: Wrong call name for 'where' command".into(), + span(spans), + )), + ); + } + + if spans.len() < 2 { + return ( + garbage(span(spans)), + Some(ParseError::MissingPositional( + "row condition".into(), + span(spans), + "where ".into(), + )), + ); + } + + let call = match working_set.find_decl(b"where", &Type::Any) { + Some(decl_id) => { + let ParsedInternalCall { + call, + error: mut err, + output, + } = parse_internal_call( + working_set, + spans[0], + &spans[1..], + decl_id, + expand_aliases_denylist, + ); + let decl = working_set.get_decl(decl_id); + + let call_span = span(spans); + + err = check_call(call_span, &decl.signature(), &call).or(err); + if err.is_some() || call.has_flag("help") { + return ( + Expression { + expr: Expr::Call(call), + span: call_span, + ty: output, + custom_completion: None, + }, + err, + ); + } + + call + } + None => { + return ( + garbage(span(spans)), + Some(ParseError::UnknownState( + "internal error: 'where' declaration not found".into(), + span(spans), + )), + ) + } + }; + + ( + Expression { + expr: Expr::Call(call), + span: span(spans), + ty: Type::Any, + custom_completion: None, + }, + None, + ) +} + +pub fn parse_where( + working_set: &mut StateWorkingSet, + spans: &[Span], + expand_aliases_denylist: &[usize], +) -> (Pipeline, Option) { + let (expression, err) = parse_where_expr(working_set, spans, expand_aliases_denylist); + (Pipeline::from_vec(vec![expression]), err) +} + #[cfg(feature = "plugin")] pub fn parse_register( working_set: &mut StateWorkingSet, diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 2b3c7369f2..9b86b531c1 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -17,7 +17,8 @@ use nu_protocol::{ use crate::parse_keywords::{ parse_alias, parse_def, parse_def_predecl, parse_export_in_block, parse_extern, parse_for, - parse_hide, parse_let, parse_module, parse_overlay, parse_source, parse_use, + parse_hide, parse_let, parse_module, parse_overlay, parse_source, parse_use, parse_where, + parse_where_expr, }; use itertools::Itertools; @@ -1892,6 +1893,7 @@ pub fn parse_full_cell_path( span: Span, expand_aliases_denylist: &[usize], ) -> (Expression, Option) { + trace!("parsing: full cell path"); let full_cell_span = span; let source = working_set.get_span_contents(span); let mut error = None; @@ -1996,10 +1998,11 @@ pub fn parse_full_cell_path( (out, true) } else if let Some(var_id) = implicit_head { + trace!("parsing: implicit head of full cell path"); ( Expression { expr: Expr::Var(var_id), - span: Span::new(0, 0), + span: head.span, ty: Type::Any, custom_completion: None, }, @@ -3035,6 +3038,7 @@ pub fn expand_to_cell_path( var_id: VarId, expand_aliases_denylist: &[usize], ) { + trace!("parsing: expanding to cell path"); if let Expression { expr: Expr::String(_), span, @@ -4589,6 +4593,8 @@ pub fn parse_math_expression( lhs_row_var_id: Option, expand_aliases_denylist: &[usize], ) -> (Expression, Option) { + trace!("parsing: math expression"); + // As the expr_stack grows, we increase the required precedence to grow larger // If, at any time, the operator we're looking at is the same or lower precedence // of what is in the expression stack, we collapse the expression stack. @@ -4765,6 +4771,8 @@ pub fn parse_expression( expand_aliases_denylist: &[usize], is_subexpression: bool, ) -> (Expression, Option) { + trace!("parsing: expression"); + let mut pos = 0; let mut shorthand = vec![]; @@ -5014,6 +5022,7 @@ pub fn parse_expression( spans[0], )), ), + b"where" => parse_where_expr(working_set, &spans[pos..], expand_aliases_denylist), #[cfg(feature = "plugin")] b"register" => ( parse_call( @@ -5149,6 +5158,7 @@ pub fn parse_builtin_commands( } b"export" => parse_export_in_block(working_set, lite_command, expand_aliases_denylist), b"hide" => parse_hide(working_set, &lite_command.parts, expand_aliases_denylist), + b"where" => parse_where(working_set, &lite_command.parts, expand_aliases_denylist), #[cfg(feature = "plugin")] b"register" => parse_register(working_set, &lite_command.parts, expand_aliases_denylist), _ => { @@ -5294,6 +5304,7 @@ pub fn parse_block( .iter() .map(|command| match command { LiteElement::Command(span, command) => { + trace!("parsing: pipeline element: command"); let (expr, err) = parse_expression( working_set, &command.parts, @@ -5309,6 +5320,7 @@ pub fn parse_block( PipelineElement::Expression(*span, expr) } LiteElement::Redirection(span, redirection, command) => { + trace!("parsing: pipeline element: redirection"); let (expr, err) = parse_string( working_set, command.parts[0], @@ -6185,8 +6197,6 @@ pub fn parse( scoped: bool, expand_aliases_denylist: &[usize], ) -> (Block, Option) { - trace!("starting top-level parse"); - let mut error = None; let span_offset = working_set.next_span_start(); diff --git a/crates/nu-protocol/src/shell_error.rs b/crates/nu-protocol/src/shell_error.rs index 22bb63533b..62e99609b8 100644 --- a/crates/nu-protocol/src/shell_error.rs +++ b/crates/nu-protocol/src/shell_error.rs @@ -814,6 +814,19 @@ Either make sure {0} is a string, or add a 'to_string' entry for it in ENV_CONVE #[label = "'{0}' is deprecated. Please use '{1}' instead."] Span, ), + /// Attempted to use a deprecated parameter. + /// + /// ## Resolution + /// + /// Check the help for the command and update your script accordingly. + #[error("Deprecated parameter {0}")] + #[diagnostic(code(nu::shell::deprecated_command), url(docsrs))] + DeprecatedParameter( + String, + String, + #[label = "Parameter '{0}' is deprecated. Please use '{1}' instead."] Span, + ), + /// Non-Unicode input received. /// /// ## Resolution