From d5b99ae316dda0c89456675bd41ee7678b87f813 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Fri, 10 Jun 2022 10:59:35 -0500 Subject: [PATCH] input and output types (#5750) * input and output types * added description * type from stored variable * string in custom value * more tests with non custom --- crates/nu-cli/src/eval_file.rs | 3 +- .../src/database/values/dsl/expression.rs | 2 +- .../values/nu_expression/custom_value.rs | 4 +- crates/nu-engine/src/eval.rs | 2 +- crates/nu-parser/src/parse_keywords.rs | 191 ++++++------ crates/nu-parser/src/parser.rs | 23 +- crates/nu-parser/tests/test_parser.rs | 288 ++++++++++++++++++ crates/nu-protocol/src/engine/command.rs | 16 +- crates/nu-protocol/src/engine/engine_state.rs | 72 +++-- crates/nu-protocol/src/engine/overlay.rs | 61 +++- crates/nu-protocol/src/ty.rs | 8 +- crates/nu-protocol/src/value/mod.rs | 2 +- 12 files changed, 523 insertions(+), 149 deletions(-) diff --git a/crates/nu-cli/src/eval_file.rs b/crates/nu-cli/src/eval_file.rs index b27626c29..8c01289c9 100644 --- a/crates/nu-cli/src/eval_file.rs +++ b/crates/nu-cli/src/eval_file.rs @@ -4,6 +4,7 @@ use log::trace; use miette::{IntoDiagnostic, Result}; use nu_engine::convert_env_values; use nu_parser::parse; +use nu_protocol::Type; use nu_protocol::{ ast::Call, engine::{EngineState, Stack, StateWorkingSet}, @@ -34,7 +35,7 @@ pub fn evaluate_file( let _ = parse(&mut working_set, Some(&path), &file, false, &[]); - if working_set.find_decl(b"main").is_some() { + if working_set.find_decl(b"main", &Type::Any).is_some() { let args = format!("main {}", args.join(" ")); if !eval_source( diff --git a/crates/nu-command/src/database/values/dsl/expression.rs b/crates/nu-command/src/database/values/dsl/expression.rs index 3fbbd99fa..d5505de01 100644 --- a/crates/nu-command/src/database/values/dsl/expression.rs +++ b/crates/nu-command/src/database/values/dsl/expression.rs @@ -91,7 +91,7 @@ impl CustomValue for ExprDb { Value::Bool { val, .. } => Ok(Expr::Value(sqlparser::ast::Value::Boolean(*val))), _ => Err(ShellError::OperatorMismatch { op_span: op, - lhs_ty: Type::Custom, + lhs_ty: Type::Custom(self.typetag_name().into()), lhs_span, rhs_ty: right.get_type(), rhs_span: right.span()?, diff --git a/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs b/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs index c223bda08..e9a16d453 100644 --- a/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs +++ b/crates/nu-command/src/dataframe/values/nu_expression/custom_value.rs @@ -126,9 +126,9 @@ fn with_operator( .into_value(lhs_span)), _ => Err(ShellError::OperatorMismatch { op_span, - lhs_ty: Type::Custom, + lhs_ty: Type::Custom(left.typetag_name().into()), lhs_span, - rhs_ty: Type::Custom, + rhs_ty: Type::Custom(right.typetag_name().into()), rhs_span, }), } diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index e7df663ce..b9dad9168 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -829,7 +829,7 @@ pub fn create_scope( }) } - for (command_name, decl_id) in commands_map { + for ((command_name, _), decl_id) in commands_map { if visibility.is_decl_id_visible(decl_id) { let mut cols = vec![]; let mut vals = vec![]; diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 34d28a239..f0c250d32 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -115,7 +115,7 @@ pub fn parse_for( // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call // Also, by creating a call, it can be checked if it matches the declaration signature - let (call, call_span) = match working_set.find_decl(b"for") { + let (call, call_span) = match working_set.find_decl(b"for", &Type::Any) { None => { return ( garbage(spans[0]), @@ -284,7 +284,7 @@ pub fn parse_def( // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call // Also, by creating a call, it can be checked if it matches the declaration signature - let (call, call_span) = match working_set.find_decl(&def_call) { + let (call, call_span) = match working_set.find_decl(&def_call, &Type::Any) { None => { return ( garbage_pipeline(spans), @@ -427,7 +427,7 @@ pub fn parse_extern( // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call // Also, by creating a call, it can be checked if it matches the declaration signature - let (call, call_span) = match working_set.find_decl(&extern_call) { + let (call, call_span) = match working_set.find_decl(&extern_call, &Type::Any) { None => { return ( garbage_pipeline(spans), @@ -520,7 +520,7 @@ pub fn parse_alias( return (Pipeline::from_vec(vec![garbage(*span)]), Some(err)); } - if let Some(decl_id) = working_set.find_decl(b"alias") { + if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) { let (call, _) = parse_internal_call( working_set, spans[0], @@ -622,7 +622,7 @@ pub fn parse_export( ); }; - let export_decl_id = if let Some(id) = working_set.find_decl(b"export") { + let export_decl_id = if let Some(id) = working_set.find_decl(b"export", &Type::Any) { id } else { return ( @@ -655,18 +655,19 @@ pub fn parse_export( parse_def(working_set, &lite_command, expand_aliases_denylist); error = error.or(err); - let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def") { - id - } else { - return ( - garbage_pipeline(spans), - None, - Some(ParseError::InternalError( - "missing 'export def' command".into(), - export_span, - )), - ); - }; + let export_def_decl_id = + if let Some(id) = working_set.find_decl(b"export def", &Type::Any) { + id + } else { + return ( + garbage_pipeline(spans), + None, + Some(ParseError::InternalError( + "missing 'export def' command".into(), + export_span, + )), + ); + }; // Trying to warp the 'def' call into the 'export def' in a very clumsy way if let Some(Expression { @@ -690,7 +691,7 @@ pub fn parse_export( if error.is_none() { let decl_name = working_set.get_span_contents(spans[2]); let decl_name = trim_quotes(decl_name); - if let Some(decl_id) = working_set.find_decl(decl_name) { + if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) { Some(Exportable::Decl(decl_id)) } else { error = error.or_else(|| { @@ -714,19 +715,19 @@ pub fn parse_export( parse_def(working_set, &lite_command, expand_aliases_denylist); error = error.or(err); - let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export def-env") - { - id - } else { - return ( - garbage_pipeline(spans), - None, - Some(ParseError::InternalError( - "missing 'export def-env' command".into(), - export_span, - )), - ); - }; + let export_def_decl_id = + if let Some(id) = working_set.find_decl(b"export def-env", &Type::Any) { + id + } else { + return ( + garbage_pipeline(spans), + None, + Some(ParseError::InternalError( + "missing 'export def-env' command".into(), + export_span, + )), + ); + }; // Trying to warp the 'def' call into the 'export def' in a very clumsy way if let Some(Expression { @@ -750,7 +751,7 @@ pub fn parse_export( if error.is_none() { let decl_name = working_set.get_span_contents(spans[2]); let decl_name = trim_quotes(decl_name); - if let Some(decl_id) = working_set.find_decl(decl_name) { + if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) { Some(Exportable::Decl(decl_id)) } else { error = error.or_else(|| { @@ -774,18 +775,19 @@ pub fn parse_export( parse_extern(working_set, &lite_command, expand_aliases_denylist); error = error.or(err); - let export_def_decl_id = if let Some(id) = working_set.find_decl(b"export extern") { - id - } else { - return ( - garbage_pipeline(spans), - None, - Some(ParseError::InternalError( - "missing 'export extern' command".into(), - export_span, - )), - ); - }; + let export_def_decl_id = + if let Some(id) = working_set.find_decl(b"export extern", &Type::Any) { + id + } else { + return ( + garbage_pipeline(spans), + None, + Some(ParseError::InternalError( + "missing 'export extern' command".into(), + export_span, + )), + ); + }; // Trying to warp the 'def' call into the 'export def' in a very clumsy way if let Some(Expression { @@ -809,7 +811,7 @@ pub fn parse_export( if error.is_none() { let decl_name = working_set.get_span_contents(spans[2]); let decl_name = trim_quotes(decl_name); - if let Some(decl_id) = working_set.find_decl(decl_name) { + if let Some(decl_id) = working_set.find_decl(decl_name, &Type::Any) { Some(Exportable::Decl(decl_id)) } else { error = error.or_else(|| { @@ -833,19 +835,19 @@ pub fn parse_export( parse_alias(working_set, &lite_command.parts, expand_aliases_denylist); error = error.or(err); - let export_alias_decl_id = if let Some(id) = working_set.find_decl(b"export alias") - { - id - } else { - return ( - garbage_pipeline(spans), - None, - Some(ParseError::InternalError( - "missing 'export alias' command".into(), - export_span, - )), - ); - }; + let export_alias_decl_id = + if let Some(id) = working_set.find_decl(b"export alias", &Type::Any) { + id + } else { + return ( + garbage_pipeline(spans), + None, + Some(ParseError::InternalError( + "missing 'export alias' command".into(), + export_span, + )), + ); + }; // Trying to warp the 'alias' call into the 'export alias' in a very clumsy way if let Some(Expression { @@ -885,7 +887,7 @@ pub fn parse_export( } } b"env" => { - if let Some(id) = working_set.find_decl(b"export env") { + if let Some(id) = working_set.find_decl(b"export env", &Type::Any) { call.decl_id = id; } else { return ( @@ -1190,7 +1192,7 @@ pub fn parse_module( }; let module_decl_id = working_set - .find_decl(b"module") + .find_decl(b"module", &Type::Any) .expect("internal error: missing module command"); let call = Box::new(Call { @@ -1239,7 +1241,7 @@ pub fn parse_use( ); } - let (call, call_span, use_decl_id) = match working_set.find_decl(b"use") { + let (call, call_span, use_decl_id) = match working_set.find_decl(b"use", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -1472,7 +1474,7 @@ pub fn parse_hide( ); } - let (call, call_span, hide_decl_id) = match working_set.find_decl(b"hide") { + let (call, call_span, hide_decl_id) = match working_set.find_decl(b"hide", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -1542,33 +1544,34 @@ pub fn parse_hide( error = error.or(err); } - let (is_module, module) = - if let Some(module_id) = working_set.find_module(&import_pattern.head.name) { - (true, working_set.get_module(module_id).clone()) - } else if import_pattern.members.is_empty() { - // The pattern head can be: - if let Some(id) = working_set.find_alias(&import_pattern.head.name) { - // an alias, - let mut module = Module::new(); - module.add_alias(&import_pattern.head.name, id); + let (is_module, module) = if let Some(module_id) = + working_set.find_module(&import_pattern.head.name) + { + (true, working_set.get_module(module_id).clone()) + } else if import_pattern.members.is_empty() { + // The pattern head can be: + if let Some(id) = working_set.find_alias(&import_pattern.head.name) { + // an alias, + let mut module = Module::new(); + module.add_alias(&import_pattern.head.name, id); - (false, module) - } else if let Some(id) = working_set.find_decl(&import_pattern.head.name) { - // a custom command, - let mut module = Module::new(); - module.add_decl(&import_pattern.head.name, id); + (false, module) + } else if let Some(id) = working_set.find_decl(&import_pattern.head.name, &Type::Any) { + // a custom command, + let mut module = Module::new(); + module.add_decl(&import_pattern.head.name, id); - (false, module) - } else { - // , or it could be an env var (handled by the engine) - (false, Module::new()) - } + (false, module) } else { - return ( - garbage_pipeline(spans), - Some(ParseError::ModuleNotFound(spans[1])), - ); - }; + // , or it could be an env var (handled by the engine) + (false, Module::new()) + } + } else { + return ( + garbage_pipeline(spans), + Some(ParseError::ModuleNotFound(spans[1])), + ); + }; // This kind of inverts the import pattern matching found in parse_use() let (aliases_to_hide, decls_to_hide) = if import_pattern.members.is_empty() { @@ -1696,7 +1699,7 @@ pub fn parse_overlay( } b"list" => { // TODO: Abstract this code blob, it's repeated all over the place: - let call = match working_set.find_decl(b"overlay list") { + let call = match working_set.find_decl(b"overlay list", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -1755,7 +1758,7 @@ pub fn parse_overlay( } } - let call = match working_set.find_decl(b"overlay") { + let call = match working_set.find_decl(b"overlay", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -1820,7 +1823,7 @@ pub fn parse_overlay_new( ); } - let (call, call_span) = match working_set.find_decl(b"overlay new") { + let (call, call_span) = match working_set.find_decl(b"overlay new", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -1911,7 +1914,7 @@ pub fn parse_overlay_add( } // TODO: Allow full import pattern as argument (requires custom naming of module/overlay) - let (call, call_span) = match working_set.find_decl(b"overlay add") { + let (call, call_span) = match working_set.find_decl(b"overlay add", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -2098,7 +2101,7 @@ pub fn parse_overlay_remove( ); } - let call = match working_set.find_decl(b"overlay remove") { + let call = match working_set.find_decl(b"overlay remove", &Type::Any) { Some(decl_id) => { let (call, mut err) = parse_internal_call( working_set, @@ -2209,7 +2212,7 @@ pub fn parse_let( return (Pipeline::from_vec(vec![garbage(*span)]), Some(err)); } - if let Some(decl_id) = working_set.find_decl(b"let") { + if let Some(decl_id) = working_set.find_decl(b"let", &Type::Any) { let cmd = working_set.get_decl(decl_id); let call_signature = cmd.signature().call_signature(); @@ -2313,7 +2316,7 @@ pub fn parse_source( let name = working_set.get_span_contents(spans[0]); if name == b"source" { - if let Some(decl_id) = working_set.find_decl(b"source") { + if let Some(decl_id) = working_set.find_decl(b"source", &Type::Any) { let cwd = working_set.get_cwd(); // Is this the right call to be using here? // Some of the others (`parse_let`) use it, some of them (`parse_hide`) don't. @@ -2445,7 +2448,7 @@ pub fn parse_register( // Parsing the spans and checking that they match the register signature // Using a parsed call makes more sense than checking for how many spans are in the call // Also, by creating a call, it can be checked if it matches the declaration signature - let (call, call_span) = match working_set.find_decl(b"register") { + let (call, call_span) = match working_set.find_decl(b"register", &Type::Any) { None => { return ( garbage_pipeline(spans), diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 873fbd184..af010b46b 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -739,7 +739,10 @@ pub fn parse_internal_call( call.decl_id = decl_id; call.head = command_span; - let signature = working_set.get_decl(decl_id).signature(); + let decl = working_set.get_decl(decl_id); + let signature = decl.signature(); + let output = decl.output_type(); + working_set.found_outputs.push(output); if signature.creates_scope { working_set.enter_scope(); @@ -1009,7 +1012,8 @@ pub fn parse_call( pos += 1; } - let mut maybe_decl_id = working_set.find_decl(&name); + let input = working_set.found_outputs.last().unwrap_or(&Type::Any); + let mut maybe_decl_id = working_set.find_decl(&name, input); while maybe_decl_id.is_none() { // Find the longest command match @@ -1031,7 +1035,7 @@ pub fn parse_call( name.extend(name_part); } } - maybe_decl_id = working_set.find_decl(&name); + maybe_decl_id = working_set.find_decl(&name, input); } if let Some(decl_id) = maybe_decl_id { @@ -2648,7 +2652,7 @@ pub fn parse_shape_name( ); let command_name = trim_quotes(split[1].as_bytes()); - let decl_id = working_set.find_decl(command_name); + let decl_id = working_set.find_decl(command_name, &Type::Any); if let Some(decl_id) = decl_id { return (SyntaxShape::Custom(Box::new(shape), decl_id), err); @@ -4511,7 +4515,7 @@ pub fn parse_expression( } }; - let with_env = working_set.find_decl(b"with-env"); + let with_env = working_set.find_decl(b"with-env", &Type::Any); if !shorthand.is_empty() { if let Some(decl_id) = with_env { @@ -4607,7 +4611,7 @@ pub fn parse_builtin_commands( b"overlay" => parse_overlay(working_set, &lite_command.parts, expand_aliases_denylist), b"source" => parse_source(working_set, &lite_command.parts, expand_aliases_denylist), b"export" => { - if let Some(decl_id) = working_set.find_decl(b"alias") { + if let Some(decl_id) = working_set.find_decl(b"alias", &Type::Any) { let (call, _) = parse_internal_call( working_set, lite_command.parts[0], @@ -4818,8 +4822,9 @@ pub fn parse_block( ); if idx == 0 { - if let Some(let_decl_id) = working_set.find_decl(b"let") { - if let Some(let_env_decl_id) = working_set.find_decl(b"let-env") { + 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) + { for expr in pipeline.expressions.iter_mut() { if let Expression { expr: Expr::Call(call), @@ -5136,7 +5141,7 @@ pub fn discover_captures_in_expr( fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: &Expression) -> Expression { let span = expr.span; - if let Some(decl_id) = working_set.find_decl(b"collect") { + if let Some(decl_id) = working_set.find_decl(b"collect", &Type::Any) { let mut output = vec![]; let var_id = working_set.next_var_id(); diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index c3e209c37..408e0b30b 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -662,3 +662,291 @@ mod range { assert!(err.is_some()); } } + +#[cfg(test)] +mod input_types { + use super::*; + use nu_protocol::{Category, Type}; + + #[derive(Clone)] + pub struct LsTest; + + impl Command for LsTest { + fn name(&self) -> &str { + "ls" + } + + fn usage(&self) -> &str { + "Mock ls command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build(self.name()).category(Category::Default) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::PipelineData, + ) -> Result { + todo!() + } + } + + #[derive(Clone)] + pub struct GroupByList; + + impl Command for GroupByList { + fn name(&self) -> &str { + "group-by" + } + + fn usage(&self) -> &str { + "Mock group-by command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build(self.name()) + .required("column", SyntaxShape::String, "column name") + .category(Category::Default) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::PipelineData, + ) -> Result { + todo!() + } + } + + #[derive(Clone)] + pub struct ToCustom; + + impl Command for ToCustom { + fn name(&self) -> &str { + "to-custom" + } + + fn usage(&self) -> &str { + "Mock converter command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build(self.name()).category(Category::Custom("custom".into())) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::PipelineData, + ) -> Result { + todo!() + } + + fn input_type(&self) -> nu_protocol::Type { + Type::Any + } + + fn output_type(&self) -> nu_protocol::Type { + Type::Custom("custom".into()) + } + } + + #[derive(Clone)] + pub struct GroupByCustom; + + impl Command for GroupByCustom { + fn name(&self) -> &str { + "group-by" + } + + fn usage(&self) -> &str { + "Mock custom group-by command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build(self.name()) + .required("column", SyntaxShape::String, "column name") + .required("other", SyntaxShape::String, "other value") + .category(Category::Custom("custom".into())) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::PipelineData, + ) -> Result { + todo!() + } + + fn input_type(&self) -> nu_protocol::Type { + Type::Custom("custom".into()) + } + + fn output_type(&self) -> nu_protocol::Type { + Type::Custom("custom".into()) + } + } + + #[derive(Clone)] + pub struct AggCustom; + + impl Command for AggCustom { + fn name(&self) -> &str { + "agg" + } + + fn usage(&self) -> &str { + "Mock custom agg command" + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build(self.name()) + .required("operation", SyntaxShape::String, "operation") + .category(Category::Custom("custom".into())) + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + _call: &nu_protocol::ast::Call, + _input: nu_protocol::PipelineData, + ) -> Result { + todo!() + } + + fn input_type(&self) -> nu_protocol::Type { + Type::Custom("custom".into()) + } + } + + fn add_declations(engine_state: &mut EngineState) { + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + working_set.add_decl(Box::new(Let)); + working_set.add_decl(Box::new(AggCustom)); + working_set.add_decl(Box::new(GroupByCustom)); + working_set.add_decl(Box::new(GroupByList)); + working_set.add_decl(Box::new(LsTest)); + working_set.add_decl(Box::new(ToCustom)); + working_set.add_decl(Box::new(Let)); + + working_set.render() + }; + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let _ = engine_state.merge_delta(delta, None, &cwd); + } + + #[test] + fn call_types_test() { + let mut engine_state = EngineState::new(); + add_declations(&mut engine_state); + + let mut working_set = StateWorkingSet::new(&engine_state); + let input = r#"ls | to-custom | group-by name other"#; + + let (block, err) = parse(&mut working_set, None, input.as_bytes(), true, &[]); + + assert!(err.is_none()); + assert!(block.len() == 1); + + let expressions = &block[0]; + assert!(expressions.len() == 3); + + match &expressions[0].expr { + Expr::Call(call) => { + let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap(); + assert_eq!(call.decl_id, expected_id) + } + _ => panic!("Expected expression Call not found"), + } + + match &expressions[1].expr { + Expr::Call(call) => { + let expected_id = working_set.find_decl(b"to-custom", &Type::Any).unwrap(); + assert_eq!(call.decl_id, expected_id) + } + _ => panic!("Expected expression Call not found"), + } + + match &expressions[2].expr { + Expr::Call(call) => { + let expected_id = working_set + .find_decl(b"group-by", &Type::Custom("custom".into())) + .unwrap(); + assert_eq!(call.decl_id, expected_id) + } + _ => panic!("Expected expression Call not found"), + } + } + + #[test] + fn storing_variable_test() { + let mut engine_state = EngineState::new(); + add_declations(&mut engine_state); + + let mut working_set = StateWorkingSet::new(&engine_state); + let input = + r#"let a = (ls | to-custom | group-by name other); let b = (1+3); $a | agg sum"#; + + let (block, err) = parse(&mut working_set, None, input.as_bytes(), true, &[]); + + assert!(err.is_none()); + assert!(block.len() == 3); + + let expressions = &block[2]; + match &expressions[1].expr { + Expr::Call(call) => { + let expected_id = working_set + .find_decl(b"agg", &Type::Custom("custom".into())) + .unwrap(); + assert_eq!(call.decl_id, expected_id) + } + _ => panic!("Expected expression Call not found"), + } + } + + #[test] + fn call_non_custom_types_test() { + let mut engine_state = EngineState::new(); + add_declations(&mut engine_state); + + let mut working_set = StateWorkingSet::new(&engine_state); + let input = r#"ls | group-by name"#; + + let (block, err) = parse(&mut working_set, None, input.as_bytes(), true, &[]); + + assert!(err.is_none()); + assert!(block.len() == 1); + + let expressions = &block[0]; + assert!(expressions.len() == 2); + + match &expressions[0].expr { + Expr::Call(call) => { + let expected_id = working_set.find_decl(b"ls", &Type::Any).unwrap(); + assert_eq!(call.decl_id, expected_id) + } + _ => panic!("Expected expression Call not found"), + } + + match &expressions[1].expr { + Expr::Call(call) => { + let expected_id = working_set.find_decl(b"group-by", &Type::Any).unwrap(); + assert_eq!(call.decl_id, expected_id) + } + _ => panic!("Expected expression Call not found"), + } + } +} diff --git a/crates/nu-protocol/src/engine/command.rs b/crates/nu-protocol/src/engine/command.rs index e0e42edaa..53ca5acb8 100644 --- a/crates/nu-protocol/src/engine/command.rs +++ b/crates/nu-protocol/src/engine/command.rs @@ -1,6 +1,6 @@ use std::path::PathBuf; -use crate::{ast::Call, BlockId, Example, PipelineData, ShellError, Signature}; +use crate::{ast::Call, BlockId, Example, PipelineData, ShellError, Signature, Type}; use super::{EngineState, Stack}; @@ -66,6 +66,20 @@ pub trait Command: Send + Sync + CommandClone { fn search_terms(&self) -> Vec<&str> { vec![] } + + // Command input type. The Type is used during parsing to find the + // correct internal command with similar names. The input type is + // obtained from the previous expression found in the pipeline + fn input_type(&self) -> Type { + Type::Any + } + + // Command output type. The output type is the value from the command + // It is used during parsing to find the next command in case there + // are commands with similar names + fn output_type(&self) -> Type { + Type::Any + } } pub trait CommandClone { diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index bb96c9122..cb8386e64 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -464,9 +464,9 @@ impl EngineState { for overlay_frame in self.active_overlays(removed_overlays).iter().rev() { visibility.append(&overlay_frame.visibility); - if let Some(decl_id) = overlay_frame.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { - return Some(*decl_id); + if let Some(decl_id) = overlay_frame.get_decl(name, &Type::Any) { + if visibility.is_decl_id_visible(&decl_id) { + return Some(decl_id); } } } @@ -533,9 +533,9 @@ impl EngineState { for overlay_frame in self.active_overlays(&[]).iter().rev() { for decl in &overlay_frame.decls { - if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(decl.0) { + if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(&decl.0 .0) { let command = self.get_decl(*decl.1); - output.push((decl.0.clone(), Some(command.usage().to_string()))); + output.push((decl.0 .0.clone(), Some(command.usage().to_string()))); } } } @@ -614,7 +614,8 @@ impl EngineState { decls_map.extend(new_decls); } - let mut decls: Vec<(Vec, DeclId)> = decls_map.into_iter().collect(); + let mut decls: Vec<(Vec, DeclId)> = + decls_map.into_iter().map(|(v, k)| (v.0, k)).collect(); decls.sort_by(|a, b| a.0.cmp(&b.0)); decls.into_iter().map(|(_, id)| id) @@ -743,6 +744,9 @@ pub struct StateWorkingSet<'a> { pub permanent_state: &'a EngineState, pub delta: StateDelta, pub external_commands: Vec>, + // Internal commands output that the next expression in the pipe will use to select a declaration + // that matches the name in the found output + pub found_outputs: Vec, } /// A delta (or change set) between the current global state and a possible future global state. Deltas @@ -867,6 +871,7 @@ impl<'a> StateWorkingSet<'a> { delta: StateDelta::new(permanent_state), permanent_state, external_commands: vec![], + found_outputs: vec![], } } @@ -918,11 +923,13 @@ impl<'a> StateWorkingSet<'a> { pub fn add_decl(&mut self, decl: Box) -> DeclId { let name = decl.name().as_bytes().to_vec(); + let input_type = decl.input_type(); self.delta.decls.push(decl); let decl_id = self.num_decls() - 1; - self.last_overlay_mut().decls.insert(name, decl_id); + self.last_overlay_mut() + .insert_decl(name, input_type, decl_id); decl_id } @@ -931,7 +938,7 @@ impl<'a> StateWorkingSet<'a> { let overlay_frame = self.last_overlay_mut(); for (name, decl_id) in decls { - overlay_frame.decls.insert(name, decl_id); + overlay_frame.insert_decl(name, Type::Any, decl_id); overlay_frame.visibility.use_decl_id(&decl_id); } } @@ -968,7 +975,7 @@ impl<'a> StateWorkingSet<'a> { let overlay_frame = self.last_overlay_mut(); if let Some(decl_id) = overlay_frame.predecls.remove(name) { - overlay_frame.decls.insert(name.into(), decl_id); + overlay_frame.insert_decl(name.into(), Type::Any, decl_id); return Some(decl_id); } @@ -998,11 +1005,11 @@ impl<'a> StateWorkingSet<'a> { visibility.append(&overlay_frame.visibility); - if let Some(decl_id) = overlay_frame.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { + if let Some(decl_id) = overlay_frame.get_decl(name, &Type::Any) { + if visibility.is_decl_id_visible(&decl_id) { // Hide decl only if it's not already hidden - overlay_frame.visibility.hide_decl_id(decl_id); - return Some(*decl_id); + overlay_frame.visibility.hide_decl_id(&decl_id); + return Some(decl_id); } } } @@ -1018,11 +1025,11 @@ impl<'a> StateWorkingSet<'a> { { visibility.append(&overlay_frame.visibility); - if let Some(decl_id) = overlay_frame.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { + if let Some(decl_id) = overlay_frame.get_decl(name, &Type::Any) { + if visibility.is_decl_id_visible(&decl_id) { // Hide decl only if it's not already hidden - self.last_overlay_mut().visibility.hide_decl_id(decl_id); - return Some(*decl_id); + self.last_overlay_mut().visibility.hide_decl_id(&decl_id); + return Some(decl_id); } } } @@ -1254,7 +1261,7 @@ impl<'a> StateWorkingSet<'a> { None } - pub fn find_decl(&self, name: &[u8]) -> Option { + pub fn find_decl(&self, name: &[u8], input: &Type) -> Option { let mut removed_overlays = vec![]; let mut visibility: Visibility = Visibility::new(); @@ -1280,9 +1287,9 @@ impl<'a> StateWorkingSet<'a> { } } - if let Some(decl_id) = overlay_frame.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { - return Some(*decl_id); + if let Some(decl_id) = overlay_frame.get_decl(name, input) { + if visibility.is_decl_id_visible(&decl_id) { + return Some(decl_id); } } } @@ -1297,9 +1304,9 @@ impl<'a> StateWorkingSet<'a> { { visibility.append(&overlay_frame.visibility); - if let Some(decl_id) = overlay_frame.decls.get(name) { - if visibility.is_decl_id_visible(decl_id) { - return Some(*decl_id); + if let Some(decl_id) = overlay_frame.get_decl(name, input) { + if visibility.is_decl_id_visible(&decl_id) { + return Some(decl_id); } } } @@ -1384,7 +1391,7 @@ impl<'a> StateWorkingSet<'a> { .rev() { for decl in &overlay_frame.decls { - if decl.0.starts_with(name) { + if decl.0 .0.starts_with(name) { return true; } } @@ -1398,7 +1405,7 @@ impl<'a> StateWorkingSet<'a> { .rev() { for decl in &overlay_frame.decls { - if decl.0.starts_with(name) { + if decl.0 .0.starts_with(name) { return true; } } @@ -1562,9 +1569,10 @@ impl<'a> StateWorkingSet<'a> { let overlay_frame = scope_frame.get_overlay(*overlay_id); for decl in &overlay_frame.decls { - if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(decl.0) { + if overlay_frame.visibility.is_decl_id_visible(decl.1) && predicate(&decl.0 .0) + { let command = self.get_decl(*decl.1); - output.push((decl.0.clone(), Some(command.usage().to_string()))); + output.push((decl.0 .0.clone(), Some(command.usage().to_string()))); } } } @@ -1718,8 +1726,8 @@ impl<'a> StateWorkingSet<'a> { if let Some(overlay_id) = self.permanent_state.find_overlay(name) { let overlay_frame = self.permanent_state.get_overlay(overlay_id); - for (decl_name, decl_id) in &overlay_frame.decls { - result.insert(decl_name.to_owned(), *decl_id); + for (decl_key, decl_id) in &overlay_frame.decls { + result.insert(decl_key.0.to_owned(), *decl_id); } } @@ -1727,8 +1735,8 @@ impl<'a> StateWorkingSet<'a> { if let Some(overlay_id) = scope_frame.find_overlay(name) { let overlay_frame = scope_frame.get_overlay(overlay_id); - for (decl_name, decl_id) in &overlay_frame.decls { - result.insert(decl_name.to_owned(), *decl_id); + for (decl_key, decl_id) in &overlay_frame.decls { + result.insert(decl_key.0.to_owned(), *decl_id); } } } diff --git a/crates/nu-protocol/src/engine/overlay.rs b/crates/nu-protocol/src/engine/overlay.rs index 7d04be0f4..dccfdf340 100644 --- a/crates/nu-protocol/src/engine/overlay.rs +++ b/crates/nu-protocol/src/engine/overlay.rs @@ -1,6 +1,7 @@ +use crate::{AliasId, DeclId, ModuleId, OverlayId, Type, VarId}; +use std::borrow::Borrow; use std::collections::HashMap; - -use crate::{AliasId, DeclId, ModuleId, OverlayId, VarId}; +use std::hash::{Hash, Hasher}; pub static DEFAULT_OVERLAY_NAME: &str = "zero"; @@ -199,7 +200,7 @@ impl ScopeFrame { pub struct OverlayFrame { pub vars: HashMap, VarId>, pub predecls: HashMap, DeclId>, // temporary storage for predeclarations - pub decls: HashMap, DeclId>, + pub decls: HashMap<(Vec, Type), DeclId>, pub aliases: HashMap, AliasId>, pub modules: HashMap, ModuleId>, pub visibility: Visibility, @@ -218,4 +219,58 @@ impl OverlayFrame { origin, } } + + pub fn insert_decl(&mut self, name: Vec, input: Type, decl_id: DeclId) -> Option { + self.decls.insert((name, input), decl_id) + } + + pub fn get_decl(&self, name: &[u8], input: &Type) -> Option { + self.decls.get(&(name, input) as &dyn DeclKey).cloned() + } +} + +trait DeclKey { + fn name(&self) -> &[u8]; + fn input(&self) -> &Type; +} + +impl Hash for dyn DeclKey + '_ { + fn hash(&self, state: &mut H) { + self.name().hash(state); + self.input().hash(state); + } +} + +impl PartialEq for dyn DeclKey + '_ { + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() && self.input() == other.input() + } +} + +impl Eq for dyn DeclKey + '_ {} + +impl<'a> DeclKey for (&'a [u8], &Type) { + fn name(&self) -> &[u8] { + self.0 + } + + fn input(&self) -> &Type { + self.1 + } +} + +impl DeclKey for (Vec, Type) { + fn name(&self) -> &[u8] { + &self.0 + } + + fn input(&self) -> &Type { + &self.1 + } +} + +impl<'a> Borrow for (Vec, Type) { + fn borrow(&self) -> &(dyn DeclKey + 'a) { + self + } } diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index 58455120c..e85e7ba90 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -4,7 +4,7 @@ use std::fmt::Display; use crate::SyntaxShape; -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Hash)] pub enum Type { Int, Float, @@ -25,7 +25,7 @@ pub enum Type { Any, Error, Binary, - Custom, + Custom(String), Signature, } @@ -51,7 +51,7 @@ impl Type { Type::Any => SyntaxShape::Any, Type::Error => SyntaxShape::Any, Type::Binary => SyntaxShape::Binary, - Type::Custom => SyntaxShape::Any, + Type::Custom(_) => SyntaxShape::Any, Type::Signature => SyntaxShape::Signature, } } @@ -95,7 +95,7 @@ impl Display for Type { Type::Any => write!(f, "any"), Type::Error => write!(f, "error"), Type::Binary => write!(f, "binary"), - Type::Custom => write!(f, "custom"), + Type::Custom(custom) => write!(f, "custom<{}>", custom), Type::Signature => write!(f, "signature"), } } diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 5a1dca973..155633acb 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -417,7 +417,7 @@ impl Value { Value::Error { .. } => Type::Error, Value::Binary { .. } => Type::Binary, Value::CellPath { .. } => Type::CellPath, - Value::CustomValue { .. } => Type::Custom, + Value::CustomValue { val, .. } => Type::Custom(val.typetag_name().into()), } }