diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 6406b33697..bc99793977 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -6,7 +6,7 @@ use nu_protocol::ast::Call; use nu_protocol::engine::{Closure, Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoSpanned, ListStream, PipelineData, RawStream, ShellError, Signature, - SyntaxShape, Type, Value, + Span, SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -82,44 +82,8 @@ impl Command for Do { let mut callee_stack = caller_stack.captures_to_stack(block.captures); let block = engine_state.get_block(block.block_id); - let params: Vec<_> = block - .signature - .required_positional - .iter() - .chain(block.signature.optional_positional.iter()) - .collect(); - - for param in params.iter().zip(&rest) { - if let Some(var_id) = param.0.var_id { - callee_stack.add_var(var_id, param.1.clone()) - } - } - - if let Some(param) = &block.signature.rest_positional { - if rest.len() > params.len() { - let mut rest_items = vec![]; - - for r in rest.into_iter().skip(params.len()) { - rest_items.push(r); - } - - let span = if let Some(rest_item) = rest_items.first() { - rest_item.span() - } else { - call.head - }; - - callee_stack.add_var( - param - .var_id - .expect("Internal error: rest positional parameter lacks var_id"), - Value::list(rest_items, span), - ) - } - } - + bind_args_to(&mut callee_stack, &block.signature, rest, call.head)?; let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); - let result = eval_block_with_early_return( engine_state, &mut callee_stack, @@ -324,6 +288,79 @@ impl Command for Do { } } +fn bind_args_to( + stack: &mut Stack, + signature: &Signature, + args: Vec, + head_span: Span, +) -> Result<(), ShellError> { + let mut val_iter = args.into_iter(); + for (param, required) in signature + .required_positional + .iter() + .map(|p| (p, true)) + .chain(signature.optional_positional.iter().map(|p| (p, false))) + { + let var_id = param + .var_id + .expect("internal error: all custom parameters must have var_ids"); + if let Some(result) = val_iter.next() { + let param_type = param.shape.to_type(); + if required && !result.get_type().is_subtype(¶m_type) { + // need to check if result is an empty list, and param_type is table or list + // nushell needs to pass type checking for the case. + let empty_list_matches = result + .as_list() + .map(|l| l.is_empty() && matches!(param_type, Type::List(_) | Type::Table(_))) + .unwrap_or(false); + + if !empty_list_matches { + return Err(ShellError::CantConvert { + to_type: param.shape.to_type().to_string(), + from_type: result.get_type().to_string(), + span: result.span(), + help: None, + }); + } + } + stack.add_var(var_id, result); + } else if let Some(value) = ¶m.default_value { + stack.add_var(var_id, value.to_owned()) + } else if !required { + stack.add_var(var_id, Value::nothing(head_span)) + } else { + return Err(ShellError::MissingParameter { + param_name: param.name.to_string(), + span: head_span, + }); + } + } + + if let Some(rest_positional) = &signature.rest_positional { + let mut rest_items = vec![]; + + for result in + val_iter.skip(signature.required_positional.len() + signature.optional_positional.len()) + { + rest_items.push(result); + } + + let span = if let Some(rest_item) = rest_items.first() { + rest_item.span() + } else { + head_span + }; + + stack.add_var( + rest_positional + .var_id + .expect("Internal error: rest positional parameter lacks var_id"), + Value::list(rest_items, span), + ) + } + Ok(()) +} + mod test { #[test] fn test_examples() { diff --git a/tests/shell/pipeline/commands/internal.rs b/tests/shell/pipeline/commands/internal.rs index c1d0dc201a..b0adca4ef3 100644 --- a/tests/shell/pipeline/commands/internal.rs +++ b/tests/shell/pipeline/commands/internal.rs @@ -521,6 +521,26 @@ fn run_dynamic_closures() { assert_eq!(actual.out, "holaaaa"); } +#[test] +fn dynamic_closure_type_check() { + let actual = nu!(r#"let closure = {|x: int| echo $x}; do $closure "aa""#); + assert!(actual.err.contains("can't convert string to int")) +} + +#[test] +fn dynamic_closure_optional_arg() { + let actual = nu!(r#"let closure = {|x: int = 3| echo $x}; do $closure"#); + assert_eq!(actual.out, "3"); + let actual = nu!(r#"let closure = {|x: int = 3| echo $x}; do $closure 10"#); + assert_eq!(actual.out, "10"); +} + +#[test] +fn dynamic_closure_rest_args() { + let actual = nu!(r#"let closure = {|...args| $args | str join ""}; do $closure 1 2 3"#); + assert_eq!(actual.out, "123"); +} + #[cfg(feature = "which-support")] #[test] fn argument_subexpression_reports_errors() {